Skip to content

23. تمرین عملی – نسخه ۱۲

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

23.1. معماری MVC

ما مدل معماری معروف به MVC (مدل–نما–کنترل‌کننده) را به شرح زیر پیاده‌سازی خواهیم کرد:

Image

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

  • ۱ – درخواست

URLهای درخواستی URL به شکل http://machine:port/contexte/….?action=uneAction&param1=v1&param2=v2&… خواهند بود. [Contrôleur principal] از یک فایل پیکربندی برای «مسیردهی» درخواست به کنترل‌کننده صحیح و اکشن صحیح در آن کنترل‌کننده استفاده خواهد کرد. برای این کار، از فیلد [action] در URL استفاده خواهد کرد. بقیهٔ URL [param1=v1&param2=v2&…] شامل پارامترهای اختیاری است که به اکشن ارسال خواهند شد. حرف C در MVC در این مورد، رشته [Contrôleur principal, Contrôleur / Action] است. اگر هیچ کنترولری نتواند اقدام درخواستی را مدیریت کند، وب‌سرور پاسخ خواهد داد که URL درخواستی یافت نشد.

  • ۲ – پردازش
    • عمل انتخاب‌شده [2a] می‌تواند از پارامترهای parami که [Contrôleur principal] به آن ارسال کرده است، استفاده کند. این پارامترها ممکن است از چندین منبع تأمین شوند:
      • مسیر [/param1/param2/…] از URL،
      • پارامترهای [param1=v1&param2=v2] از URL,
      • از پارامترهای ارسال‌شده توسط مرورگر در درخواست آن؛
    • هنگام پردازش درخواست کاربر، ممکن است این اقدام به لایه [métier] [2b] نیاز داشته باشد. پس از پردازش درخواست مشتری، ممکن است پاسخ‌های مختلفی ایجاد شود. یک مثال معمول عبارت است از:
      • یک پاسخ خطا اگر درخواست نتوانست به درستی پردازش شود؛
      • در غیر این صورت، یک پاسخ تأیید؛
    • [Contrôleur / Action] پاسخ خود، [2c]، را به همراه یک کد وضعیت به کنترل‌کننده اصلی بازمی‌گرداند. این کدهای وضعیت، وضعیت فعلی برنامه را به طور منحصربه‌فردی نشان می‌دهند. این کدها یا کدهای موفقیت هستند یا کدهای خطا؛
  • ۳ – پاسخ
    • بسته به اینکه آیا کلاینت پاسخ jSON را درخواست کرده باشد، XML یا HTML، [Contrôleur principal] نوع پاسخ مناسب، [3a]، را ایجاد کرده و به آن دستور می‌دهد که پاسخ را برای کلاینت ارسال کند. [Contrôleur principal] هم پاسخ و هم کد وضعیت ارائه‌شده توسط [Contrôleur / Action] که اجرا شده بود را به آن منتقل می‌کند؛
    • اگر پاسخ مورد نظر از نوع jSON یا XML باشد، پاسخ انتخاب‌شده پاسخ ارائه‌شده از [Contrôleur / Action] را قالب‌بندی کرده و از طریق [3c] ارسال می‌کند. کلاینتی که قادر به پردازش این پاسخ است ممکن است یک اسکریپت کنسول PHP یا یک اسکریپت جاوااسکریپت تعبیه‌شده در یک صفحه HTML باشد؛
    • اگر پاسخ مورد نظر از نوع HTML باشد، پاسخ انتخاب‌شده با استفاده از کد وضعیت ارائه‌شده به آن، یکی از نماهای HTML یا [Vuei] را انتخاب خواهد کرد. این نما برای MVC است. هر کد وضعیت با یک نما مطابقت دارد. این نما V پاسخ اجرا شده از [Contrôleur / Action] را نمایش خواهد داد. این [ویو] برای نمایش داده‌های این پاسخ از HTML، CSS و جاوااسکریپت استفاده می‌کند. به این داده‌ها، مدل ویو (view model) گفته می‌شود. این «M» در MVC است. کلاینت معمولاً یک مرورگر وب است؛

اکنون بیایید ارتباط بین معماری وب MVC و معماری لایه‌ای را روشن کنیم. بسته به نحوه تعریف مدل، این دو مفهوم ممکن است مرتبط باشند یا نباشند. بیایید یک برنامه وب تک‌لایه MVC را در نظر بگیریم:

Image

در مثال بالا، هر یک از اجزای [Contrôleur / Action] بخشی از لایه‌های [métier] و [dao] را در خود جای داده‌اند. در لایه [web]، در واقع یک معماری MVC وجود دارد، اما کل برنامه به صورت لایه‌لایه نیست. در اینجا تنها یک لایه وجود دارد که همه کارها را انجام می‌دهد.

اکنون، بیایید یک معماری وب چندلایه را در نظر بگیریم:

Image

لایه [web] را می‌توان بدون پیروی از مدل MVC پیاده‌سازی کرد. بنابراین ما یک معماری چندلایه داریم، اما لایه وب مدل MVC را پیاده‌سازی نمی‌کند.

برای مثال، در محیط .NET، لایه [web]می‌تواند با استفاده از ASP.NET و MVC پیاده‌سازی شود که منجر به یک معماری لایه‌ای با یک لایه [web] از نوع MVC می‌شود. پس از انجام این کار، این لایه ASP.NET و MVC را می‌توان با یک لایه استاندارد ASP.NET (WebForms) جایگزین کرد و بقیه را حفظ کرد (منطق کسب‌وکار، DAO، راننده) دقیقاً همان‌طور که هست. سپس ما یک معماری لایه‌ای داریم که در آن لایه [web] دیگر از نوع MVC نیست.

در MVC بیان کردیم که مدل M همان نما V است، c.a.d – مجموعه‌ای از داده‌هایی که توسط نما V نمایش داده می‌شوند. تعریف دیگری از مدل M برای MVC ارائه شده است:

Image

بسیاری از نویسندگان معتقدند که آنچه در سمت راست لایه [web] قرار دارد، مدل M از MVC را تشکیل می‌دهد. برای جلوگیری از ابهام، می‌توان به موارد زیر اشاره کرد:

  • مدل دامنه زمانی که به همه چیز در سمت راست لایه [web] اشاره می‌شود؛
  • مدل نما هنگام ارجاع به داده‌های نمایش‌داده‌شده توسط یک نما V؛

23.2. درخت پروژه NetBeans

برای پروژه NetBeans، ما معماری‌ای را اتخاذ خواهیم کرد که مدل MVC را منعکس می‌کند:

Image

  • [3]: [main.php] کنترل‌کننده اصلی مدل MVC ما است. این C در MVC است؛
  • [4]: پوشه [Controllers] شامل کنترل‌کننده‌های ثانویه خواهد بود. هر یک از آن‌ها یک اقدام خاص را مدیریت می‌کند. این اقدام در URL مشخص شده است، برای مثال […/main.php?action=authentifier-utilisateur]. با این اقدام، [Contrôleur principal] و [main.php] یک [Contrôleur secondaire] – در این مورد، [AuthentifierUtilisateurController] – را برای پردازش اقدام درخواستی انتخاب خواهند کرد. این کنترل‌کننده‌ها همچنین بخشی از C مربوط به MVC هستند؛
  • [5]: پوشه [Model] شامل لایه‌های برنامه [métier] و [dao] خواهد بود. طبق اصطلاحات پذیرفته‌شده قبلی، این عناصر نمایانگر مدل دامنه هستند و بر اساس اصطلاحات پذیرفته‌شده برای «M»، ممکن است نمایانگر «M» در MVC باشند؛
  • [6]: پوشه [Responses] شامل کلاس‌های مسئول ارسال پاسخ به مشتری است. برای هر نوع پاسخ مورد نظر یک کلاس وجود دارد:
    • [JsonResponse]: برای پاسخی از نوع jSON;
    • [XmlResponse]: برای پاسخی از نوع XML;
    • [HtmlResponse]: برای پاسخی HTML;
  • [7]: پوشه [Views] حاوی ویوها HTML است، زمانی که یک پاسخ HTML مورد نیاز باشد. این V در MVC است. آنها توسط کلاس [HtmlResponse] فعال می‌شوند که داده‌های قابل نمایش را به آنها منتقل می‌کند. این داده‌ها قالب نما را تشکیل می‌دهند. بر اساس اصطلاح‌شناسی پذیرفته‌شده برای M، این داده‌ها ممکن است M در MVC باشند؛
  • [8]: پوشه [Utilities] شامل ابزارهای کمکی است:
    • [Logger]: کلاسی که امکان ثبت لاگ در یک فایل متنی را فراهم می‌کند؛
    • [Sendmail]: کلاسی که به شما امکان ارسال ایمیل می‌دهد؛
  • [9]: پوشه [Logs] حاوی فایل لاگ [logs.txt] است؛
  • [10]: پوشه [Entities] شامل کلاس‌هایی است که توسط کنترل‌کننده‌های مختلف استفاده می‌شوند؛

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

  • [main.php] [3] درخواست را دریافت می‌کند؛
  • پس از انجام برخی بررسی‌های مقدماتی (آیا این اقدام یکی از اقدامات پذیرفته‌شده است؟)، درخواست را به کنترل‌کننده ثانویه [4] که مسئول پردازش این اقدام است، ارجاع می‌دهد؛
  • کنترل‌کننده ثانویه وظیفه خود را انجام می‌دهد. در این کار، ممکن است به لایه‌های [métier]، [dao] و [5]، و همچنین به اجزای موجود در پوشه [10] نیاز داشته باشد. پاسخ خود را به کنترل‌کننده اصلی [main.php] که آن را فعال کرده بود، بازمی‌گرداند؛
  • بسته به نوع پاسخی که مشتری از [jSON, XML, HTML] درخواست کرده است، کنترل‌کننده اصلی [main.php] یکی از پاسخ‌های موجود در پوشه [Responses] یا [6] را فعال می‌کند؛
  • پاسخ‌های [JsonResponse, XmlResponse] به ترتیب پاسخ jSON یا XML را برای مشتری ارسال می‌کنند؛
  • پاسخ [HtmlResponse] از یکی از ویوها در پوشه [Views] [7] برای ارسال پاسخ HTML به کلاینت استفاده می‌کند؛
  • کنترل‌کننده‌های مختلف به کلاس [Logger] در پوشه [8] دسترسی دارند تا لاگ‌ها را در فایل لاگ در پوشه [9] بنویسند. موارد زیر ثبت می‌شوند:
    • اقدام درخواستی؛
    • پاسخ از کنترل‌کننده آن. این صرف‌نظر از نوع [jSON, XML, HTML] درخواستی، در قالب jSON ثبت می‌شود؛
  • در صورت رخ دادن یک خطای مرگبار (HTTP_INTERNAL_SERVER_ERROR)، کنترل‌کننده اصلی [main.php] با استفاده از کلاس [SendMail] از پوشه [8] ایمیلی برای مدیر ارسال می‌کند؛

23.3. اقدامات برنامه

کلاینت اقدام قابل اجرا را در قالب پارامتر [action] درون URL [/main.php?action=xxx] به سرور وب ارسال می‌کند. اقدامات مجاز در فایل [config.json] فهرست شده‌اند که کنترل‌کننده اصلی [main.php] را پیکربندی می‌کند:


"actions":
            {
                "init-session": "\\InitSessionController",
                "authentifier-utilisateur": "\\AuthentifierUtilisateurController",
                "calculer-impot": "\\CalculerImpotController",
                "lister-simulations": "\\ListerSimulationsController",
                "supprimer-simulation": "\\SupprimerSimulationController",
                "fin-session": "\\FinSessionController",
                "afficher-calcul-impot": "\\AfficherCalculImpotController"
},
  • خط ۱: کلید [actions] از فرهنگ لغت jSON;
  • خطوط ۳–۹: یک فرهنگ لغت [action:contrôleur]. هر اقدام با کنترل‌کننده ثانویه‌ای که مسئول پردازش آن است مرتبط است؛
  • خط ۳: [init-session]: یک جلسه شبیه‌سازی محاسبه مالیات را آغاز می‌کند. این اقدام نوع پاسخ‌های مورد نیاز را مشخص می‌کند: [jSON, XML, HTML];
  • خط ۴: پس از تعیین نوع جلسه، مشتری باید با استفاده از اقدام [authentifier-utilisateur] احراز هویت کند. تا زمانی که احراز هویت نشده‌اند، تمام اقدامات دیگر به استثنای [init-session] ممنوع است؛
  • خط ۵: پس از احراز هویت، کلاینت قادر خواهد بود یک سری محاسبات مالیاتی را با استفاده از اقدام [calculer-impot] انجام دهد؛
  • خط ۶: در هر زمان، مشتری می‌تواند با استفاده از اقدام [lister-simulations] درخواست مشاهده فهرست شبیه‌سازی‌هایی را که انجام داده است، بدهد؛
  • خط ۷: آنها می‌توانند برخی از این موارد را با استفاده از اقدام [supprimer-simulation] حذف کنند؛
  • خط ۸: مشتری جلسه شبیه‌سازی خود را با استفاده از اقدام [fin-session] پایان می‌دهد. از این نقطه به بعد، اگر بخواهد از برنامه استفاده کند، باید دوباره وارد شود؛
  • خط ۹: در برنامه HTML، اقدام [afficher-calcul-impot] فرم محاسبه مالیات را نمایش می‌دهد؛

23.4. پیکربندی برنامه وب

این برنامه توسط فایل زیر پیکربندی می‌شود: jSON [config.json]:


{
    "databaseFilename": "database.json",
    "rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-12",
    "relativeDependencies": [

        "/Entities/BaseEntity.php",
        "/Entities/Simulation.php",
        "/Entities/Database.php",
        "/Entities/TaxAdminData.php",
        "/Entities/ExceptionImpots.php",

        "/Utilities/Logger.php",
        "/Utilities/SendAdminMail.php",        

        "/Model/InterfaceServerDao.php",
        "/Model/ServerDao.php",
        "/Model/ServerDaoWithSession.php",
        "/Model/InterfaceServerMetier.php",
        "/Model/ServerMetier.php",

        "/Responses/InterfaceResponse.php",
        "/Responses/ParentResponse.php",
        "/Responses/JsonResponse.php",
        "/Responses/XmlResponse.php",
        "/Responses/HtmlResponse.php",

        "/Controllers/InterfaceController.php",
        "/Controllers/InitSessionController.php",
        "/Controllers/ListerSimulationsController.php",
        "/Controllers/AuthentifierUtilisateurController.php",
        "/Controllers/CalculerImpotController.php",
        "/Controllers/SupprimerSimulationController.php",
        "/Controllers/FinSessionController.php",
        "/Controllers/AfficherCalculImpotController.php"
    ],
    "absoluteDependencies": [
        "C:/myprograms/laragon-lite/www/vendor/autoload.php",
        "C:/myprograms/laragon-lite/www/vendor/predis/predis/autoload.php"
    ],
    "users": [
        {
            "login": "admin",
            "passwd": "admin"
        }
    ],
    "adminMail": {
        "smtp-server": "localhost",
        "smtp-port": "25",
        "from": "guest@localhost",
        "to": "guest@localhost",
        "subject": "plantage du serveur de calcul d'impôts",
        "tls": "FALSE",
        "attachments": []
    },
    "logsFilename": "Logs/logs.txt",
    "actions":
            {
                "init-session": "\\InitSessionController",
                "authentifier-utilisateur": "\\AuthentifierUtilisateurController",
                "calculer-impot": "\\CalculerImpotController",
                "lister-simulations": "\\ListerSimulationsController",
                "supprimer-simulation": "\\SupprimerSimulationController",
                "fin-session": "\\FinSessionController",
                "afficher-calcul-impot": "\\AfficherCalculImpotController"
            },
    "types": {
        "json": "\\JsonResponse",
        "html": "\\HtmlResponse",
        "xml": "\\XmlResponse"
    },
    "vues": {
        "vue-authentification.php": [700, 221, 400],
        "vue-calcul-impot.php": [200, 300, 341, 350, 800],
        "vue-liste-simulations.php": [500, 600]
    },
    "vue-erreurs": "vue-erreurs.php"
}

نظرات

  • خط ۲: نام فایل jSON حاوی پیکربندی دسترسی به پایگاه داده؛
  • خطوط ۳–۳۹: پیکربندی وابستگی‌های پروژه. تمام اسکریپت‌های PHP در ساختار دایرکتوری پروژه در اینجا فهرست شده‌اند؛
  • خطوط ۴۰–۴۴: کاربری که مجاز به استفاده از برنامه است؛
  • خطوط ۴۶–۵۴: آدرس ایمیل مدیر برنامه؛
  • خط ۵۵: مسیر فایل لاگ؛
  • خطوط ۵۶–۶۵: ارتباطات [action => contrôleur secondaire chargé de la traiter]؛
  • خطوط ۶۶–۷۰: انجمن‌های [type de réponse => classe Response chargée denvoyer la réponse au client];
  • خطوط ۷۱–۷۵: انجمن‌های [vue HTML => tableau des codes détat menant à cette vue];
  • خط ۷۶: نما [vue-erreurs] در یک جلسه HTML نمایش داده می‌شود هرگاه یک خطای غیرعادی رخ دهد:
    • یک برنامه کاربردی مانند jSON یا XML معمولاً از طریق یک کلاینت برنامه‌ریزی‌شده پرس‌وجو می‌شود. کلاینت پارامترهایی را به سرور ارسال می‌کند که ممکن است ناقص یا نادرست باشند. کنترل‌کننده‌ها این موارد را مدیریت کرده و کدهای خطا را به کلاینت بازمی‌گردانند. تمام موارد خطای ممکن باید مدیریت شوند؛
    • در یک برنامه کاربردی HTML، اوضاع کمی متفاوت است. در استفاده عادی، برنامه کاربردی وب تنها از زیرمجموعه‌ای از موارد استفاده ممکن برای کلاینت‌های jSON و XML بهره می‌برد. بیایید یک مثال بزنیم: اکشن [calculer-impot] منتظر سه پارامتر ارسال‌شده (که توسط POST ارسال می‌شوند) است: [marié, enfants, salaire].
      • اگر ما یک کلاینت jSON داشته باشیم که به ما اجازه می‌دهد URL را به صورت دستی وارد کنیم، ممکن است عملیاتی را با [calculer-impot] به جای POST با GET درخواست کنند، یا با POST بدون هیچ‌یک از پارامترهای ارسال‌شده (POST) زمانی که سه پارامتر مورد نیاز است، و غیره… سرور jSON باید از همه این موارد پشتیبانی کند؛
      • با یک برنامه وب، اقدام [calculer-impot] از طریق یک فرم وب درخواست خواهد شد که در آن هیچ‌یک از دو سناریوی قبلی امکان‌پذیر نیست: اقدام [calculer-impot] با پارامترهای POST و سه پارامتر [marié, enfants, salaire] فراخوانی خواهد شد. ممکن است برخی از این پارامترها دارای مقدار نادرستی باشند اما همچنان موجود خواهند بود. با این حال، کاربر می‌تواند با وارد کردن دستی URL در مرورگر، برخی خطاها را بازتولید کند. به دلایل امنیتی، این سناریو باید مدیریت شود؛
      • نما [vue-erreurs] هر زمان که یک کنترل‌کننده ثانویه کد وضعیت ناسازگاری با برنامه وب را بازگرداند، نمایش داده خواهد شد؛ یعنی کد وضعیتی که در خطوط ۷۲ تا ۷۴ فایل پیکربندی فهرست نشده است. ما این راه‌حل را به دلایل آموزشی انتخاب کرده‌ایم. گزینه ممکن دیگر این است که هیچ اقدامی انجام ندهیم و صرفاً نمای فعلی را که در مرورگر مشتری نمایش داده می‌شود، دوباره نمایش دهیم، تا کاربر چنین تصور کند که سرور به درخواست‌های دست‌ساز URL او پاسخ نمی‌دهد؛

23.5. نصب ابزارها و کتابخانه‌ها

23.5.1. Postman

[Postman] ابزاری است که به ما امکان می‌دهد تا از URLهای مختلف در اپلیکیشن وب خود پرس‌وجو کنیم. این ابزار به ما اجازه می‌دهد:

  • استفاده از هر URL: این‌ها دست‌ساز هستند؛
  • ارسال درخواست‌ها به سرور وب از طریق GET، POST، PUT، OPTIONS…؛
  • برای مشخص کردن پارامترها برای GET یا POST;
  • برای تنظیم هدرهای HTTP برای درخواست؛
  • برای دریافت پاسخ در قالب jSON, XML, HTML,
  • برای دسترسی به هدرهای HTTP پاسخ. بنابراین ما به پاسخ کامل HTTP از سرور دسترسی داریم؛

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

[Postman] در URL [https://www.getpostman.com/downloads/] در دسترس است. نسخه موجود در ژوئن ۲۰۱۹، نسخه ۷.۲ است. این نسخه دارای یک باگ است: هنگام ارسال درخواست‌های متوالی به وب‌سرور مورد نظر، کلاینت [Postman 7.2] به طور خودکار کوکی‌هایی را که توسط سرور برای آن ارسال شده، به ویژه کوکی جلسه (session cookie)، بازنمی‌گرداند. بنابراین برای حفظ جلسه، کوکی جلسه باید به‌صورت دستی در هدرهای HTTP درخواست‌های بعدی کپی شود. این کار به‌ویژه پیچیده نیست، اما نامناسب است. این یک باگ است که در نسخه‌های قبلی وجود نداشت. تیم [Postman] با اطلاع از این باگ، آن را در یک نسخه آلفا (که ممکن است ناپایدار باشد) به نام [Postman Canary] برطرف کرده است که در URL [https://www.getpostman.com/downloads/canary] موجود است. این نسخه مورد استفاده در اینجا است. ما نحوه نصب آن را توضیح خواهیم داد. اگر یک نسخه پایدار ([Postman 7.3] یا جدیدتر) در دسترس باشد، می‌توانید آن را دانلود کنید: احتمالاً این باگ در آن برطرف شده است.

نصب نسخهٔ خود از [Postman] را ادامه دهید. در حین نصب، از شما خواسته می‌شود یک حساب کاربری ایجاد کنید: این کار در اینجا مورد نیاز نیست. حساب [Postman] برای همگام‌سازی دستگاه‌های مختلف استفاده می‌شود تا پیکربندی یکی از آن‌ها روی دستگاه دیگر کپی شود. هیچ‌یک از این موارد در اینجا کاربرد ندارد.

پس از نصب، [Postman] رابط کاربری زیر را نمایش می‌دهد:

Image

  • در [2-3]، می‌توانید به تنظیمات محصول دسترسی داشته باشید؛

Image

  • در [6]، نسخه‌ای که در این سند استفاده شده است؛
  • اگر حساب کاربری ایجاد کرده باشید، همگام‌سازی بین رایانه شما و یک سرور راه دور [Postman] انجام می‌شود. این موضوع با چرخ دندانه در حال چرخش [7] نشان داده می‌شود که هر بار که در پروژه [Postman] تغییراتی ایجاد می‌کنید، ظاهر می‌شود. برای متوقف کردن این همگام‌سازی غیرضروری، از [8-9] خارج شوید؛

23.5.2. کتابخانه Symfony / Serializer

برای سریالی‌سازی اشیاء در jSON و XML، از کتابخانه [Symfony / Serializer] استفاده خواهیم کرد. این کتابخانه دو مزیت ارائه می‌دهد:

  • استفاده از آن برای سریالیز کردن به jSON یا XML یکپارچه است: این امر از نیاز به یادگیری دو کتابخانه API (رابط برنامه‌نویسی کاربردی) متفاوت جلوگیری می‌کند؛
  • این کتابخانه می‌تواند به‌طور بومی اشیاء را به jSON یا XML سریال کند، حتی اگر ویژگی‌های آن‌ها خصوصی باشند. شایان ذکر است که در jSON، برای سریالیزه کردن یک شی، کلاس آن باید رابط [\JsonSerializable] را پیاده‌سازی می‌کرد. نتیجه‌ای که در آن زمان به دست می‌آمد، رشته jSON بود که یک آرایهٔ asociative را با ویژگی‌های کلاس به عنوان کلیدها نشان می‌داد. هنگامی که این رشته jSON سریالیز می‌شد، آرایهٔ asociative اولیه بازیابی می‌شد، که سپس باید به یک شیء از کلاسی که سریالیز شده بود تبدیل می‌شد. با `[Symfony / Serializer]`، دسریالیزاسیون بلافاصله یک شیء از کلاس سریالی‌شده تولید می‌کند. این ساده‌تر است؛

مستندات کتابخانه [Symfony / Serializer] در URL: [https://symfony.com/doc/current/components/serializer.html] (ژوئن ۲۰۱۹) موجود است.

برای نصب این کتابخانه، یک ترمینال Laragon را باز کنید (به بخش پیوندها مراجعه کنید) و دستور زیر را وارد کنید:

Image

  • [1]، دستور نصب کتابخانه [symfony/serializer]؛
  • [2]، کتابخانهٔ دیگری که برای پروژهٔ ما لازم است: امکان سریالی‌سازی اشیاء را فراهم می‌کند؛

Image

23.6. اشیاء برنامه

Image

اشیاء [BaseEntity, Database, ExceptionImpots, TaxAdminData] از نسخه 08 سرویس وب مورد استفاده قرار گرفته‌اند (به بخش «لینک» مراجعه کنید).

کلاس [Simulation] برای بسته‌بندی عناصر یک شبیه‌سازی محاسبه مالیات استفاده خواهد شد:


<?php

namespace Application;

class Simulation extends BaseEntity {
  //ویژگی‌های شبیه‌سازی محاسبه مالیات
  protected $marié;
  protected $enfants;
  protected $salaire;
  protected $impôt;
  protected $surcôte;
  protected $décôte;
  protected $réduction;
  protected $taux;

  // گیرنده‌ها
  public function getMarié() {
    return $this->marié;
  }

  public function getEnfants() {
    return $this->enfants;
  }

  public function getSalaire() {
    return $this->salaire;
  }

  public function getImpôt() {
    return $this->impôt;
  }

  public function getSurcôte() {
    return $this->surcôte;
  }

  public function getDécôte() {
    return $this->décôte;
  }

  public function getRéduction() {
    return $this->réduction;
  }

  public function getTaux() {
    return $this->taux;
  }

}

توضیحات

  • خط ۵: کلاس [Simulation] از کلاس [BaseEntity] ارث می‌برد و بنابراین متدهای زیر را به ارث می‌برد:
    • [setFromArrayOfAttributes($arrayOfAttributes)]: که برای مقداردهی اولیه ویژگی‌های کلاس استفاده می‌شود؛
    • [__toString]: که رشته jSON را برای شی بازمی‌گرداند؛
  • خطوط ۷–۱۴: ویژگی‌های شبیه‌سازی؛
  • خطوط ۱۶–۴۷: گترهای کلاس؛

23.7. مفیدیت‌های برنامه

Image

کلاس [Logger] امکان ثبت رویدادها در یک فایل متنی را فراهم می‌کند. این کلاس در بخش پیوند داده شده توضیح داده شده است.

کلاس [SendAdminMail] امکان ارسال ایمیل به مدیر برنامه را فراهم می‌کند. این کلاس در بخش مرتبط توضیح داده شده است.

23.8. لایه‌های [métier] و [dao]

Image

Image

کلاس‌ها و رابط‌های لایه‌های [métier] و [dao] در پوشه [Model] گروه‌بندی شده‌اند. همه آن‌ها در نسخه‌های قبلی تعریف و استفاده شده‌اند:

ExceptionImpots
کلاس استثناهایی که توسط لایه [dao] پرتاب می‌شوند. در بخش 'link' تعریف شده است.
InterfaceServerDao
رابطی که توسط لایه [dao] سرور پیاده‌سازی شده است. در بخش «link» تعریف شده است.
ServerDao
پیاده‌سازی رابط [InterfaceServerDao]. لایه [dao] سرور را پیاده‌سازی می‌کند. در بخش «link» تعریف شده است.
ServerDaoWithSession
پیاده‌سازی رابط [InterfaceServerDao]. لایه [dao] سرور را پیاده‌سازی می‌کند. در بخش «Link» تعریف شده است.
InterfaceServerMetier
رابط پیاده‌سازی‌شده توسط لایه [métier] سرور. تعریف‌شده در بخش «link».
ServerMetier
پیاده‌سازی رابط [InterfaceMetier]. لایه [metier] سرور را پیاده‌سازی می‌کند. در بخش «link» تعریف شده است.

برنامه‌ای که در حال حاضر در حال توسعه است، از عناصری که قبلاً ارائه و مورد استفاده قرار گرفته‌اند، به طور گسترده استفاده می‌کند:

  • لایه‌های [métier] و [dao]؛
  • ابزارهای [Logger] و [SendAdminMail]؛
  • اشیاء [ExceptionImpots, TaxAdminData, Database

ما بر لایه [web] برنامه تمرکز خواهیم کرد:

Image

23.9. کنترل‌کننده اصلی [main.php]

23.9.1. مقدمه

Image

  • [1-2]: کنترل‌کننده اصلی [main.php] [1] توسط فایل [config.json] [2] پیکربندی می‌شود؛

بیایید موقعیت کنترل‌کننده اصلی را در معماری خود MVC به یاد آوریم:

Image

در [1]، کنترل‌کننده اصلی [main.php] اولین عنصر در معماری MVC برای پردازش درخواست مشتری است. این کنترل‌کننده چندین نقش دارد:

  • اولاً، بررسی‌های پایه‌ای را انجام می‌دهد:
    • آیا فایل پیکربندی آن وجود دارد و معتبر است؛
    • تمام وابستگی‌های پروژه را بارگذاری می‌کند. این به معنای بارگذاری تمام اجزای معماری MVC است؛
    • آیا اقدام درخواستی مشخص شده است؟ اگر چنین است، آیا معتبر است؟
    • اگر اقدام درخواستی معتبر باشد، کنترل‌کننده ثانویه‌ای را که آن را پردازش خواهد کرد انتخاب می‌کند و اطلاعات مورد نیاز آن را به آن منتقل می‌کند: درخواست، جلسه و پیکربندی برنامه؛
    • پاسخ را از کنترل‌کننده ثانویه ([2c]) بازیابی کنید. بسته به نوع (jSON, XML، HTML) که توسط کلاینت درخواست شده است، پاسخ را انتخاب [3a] کنید (JsonResponse، XmlResponse، HtmlResponse) مسئول ارسال پاسخ به کلاینت و ارائه تمام اطلاعات مورد نیاز آن (درخواست HTTP، جلسه، پیکربندی برنامه، پاسخ از کنترل‌کننده ثانویه) است؛
    • پس از ارسال این پاسخ ([3c])، هر منبعی را که ممکن است برای پردازش درخواست تخصیص داده شده باشد، آزاد کنید؛

23.9.2. [main.php] - 1

کد کنترل‌کننده اصلی [main.php] به شرح زیر است:


<?php

// پابندی سخت‌گیرانه به انواع اعلام‌شده پارامترهای تابع
declare (strict_types=1);

// فضای نام
namespace Application;

//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;

// مدیریت خطا توسط PHP
//ini_set("display_errors", "0");
error_reporting(E_ALL && !E_WARNING && !E_NOTICE);
//بازیابی پیکربندی
$configFilename = "config.json";
$fileContents = \file_get_contents($configFilename);
$erreur = FALSE;
//خطا؟
if (!$fileContents) {
  //خطا را ثبت کنید
  $état = 131;
  $erreur = TRUE;
  $message = "Le fichier de configuration [$configFilename] n'existe pas";
}
if (!$erreur) {
  // کد JSON را از فایل پیکربندی در یک آرایهٔ asociative بارگذاری کنید
  $config = \json_decode($fileContents, true);
  //خطا؟
  if (!$config) {
    //ثبت خطا
    $erreur = TRUE;
    $état = 132;
    $message = "Le fichier de configuration [$configFilename] n'a pu être exploité correctement";
  }
}
//خطا؟
if ($erreur) {
  //آماده‌سازی پاسخ سرور JSON
  //نمی‌توان از فایل پیکربندی استفاده کرد
  //وابستگی‌های Symfony
  require_once "C:/myprograms/laragon-lite/www/vendor/autoload.php";
  //در حال آماده‌سازی پاسخ
  $response = new Response();
  $response->headers->set("content-type", "application/json");
  $response->setCharset("utf-8");
  // کد وضعیت
  $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
  // محتوا
  $response->setContent(json_encode(["action" => "", "état" => $état, "réponse" => $message], JSON_UNESCAPED_UNICODE));
  //ارسال
  $response->send();
  // پایان
  exit;
}

نظرات

  • خطوط ۱۰–۱۲: کنترلر اصلی از اشیاء Symfony زیر استفاده می‌کند:
    • [Request]: درخواست HTTP که در حال پردازش است؛
    • [Session]: جلسهٔ برنامهٔ وب؛
    • [Response]: پاسخ HTTP به کلاینت؛
  • خط ۱۵: در طول توسعه، این خط به صورت توضیحی باقی می‌ماند: خطاهای PHP سپس در جریان متنی که به کلاینت ارسال می‌شود، گنجانده می‌شوند. اگر کلاینت یک مرورگر باشد، این امکان را فراهم می‌کند که خطاهای مشاهده‌شده توسط سرور نمایش داده شوند. این امر به عیب‌یابی کمک می‌کند؛
  • خط ۱۶: همه خطاها گزارش می‌شوند (E_ALL) به جز هشدارها (! E_WARNING) و اطلاعات غیرکشنده (! E_NOTICE). برای مثال، اگر یک فایل قابل باز شدن نباشد، PHP خطایی از نوع [E_NOTICE] تولید می‌کند. اگر خط ۱۵ نمایش خطا را فعال کند، خطای باز کردن فایل در مرورگر مشتری ظاهر می‌شود. این موضوع در صورتی که فراموش کرده باشید نتیجه باز کردن فایل را تست کنید، مشکلی ندارد، اما اگر قصد تست آن را داشته باشید، کمتر قابل قبول است: در این صورت یک خط [notice] پاسخ سرور به کلاینت را شلوغ می‌کند. در طول فاز توسعه، خط 16 نیز باید به صورت کامنت درآید: شما نمی‌خواهید هیچ خطایی را از دست بدهید؛
  • خط ۱۹: فایل پیکربندی خوانده می‌شود؛
  • خطوط ۲۲–۲۷: اگر این عملیات خواندن با شکست مواجه شود، خطا ثبت می‌شود (خط ۲۵)، برنامه به وضعیت [131] تنظیم می‌شود و یک پیام خطا آماده می‌گردد؛
  • خط ۳۰: رشته jSON از فایل پیکربندی رمزگشایی می‌شود؛
  • خطوط ۳۲–۳۷: اگر این رمزگشایی ناموفق باشد، خطا را ثبت کنید (خط ۳۴)، وضعیت برنامه را روی [132] تنظیم کرده و یک پیام خطا آماده کنید؛
  • خطوط ۴۰–۵۷: اگر هنگام خواندن فایل پیکربندی خطایی رخ دهد، فرایند نمی‌تواند ادامه یابد. سپس یک پاسخ jSON برای مشتری آماده می‌شود:
  • خط ۴۴: از آنجایی که فایل پیکربندی خوانده نشده است، فایل [autoload] که توسط [Symfony] مورد نیاز است، باید به صورت دستی وارد شود؛
  • خطوط ۴۶–۴۷: پاسخی با کد jSON آماده می‌شود؛
  • خط ۵۰: کد پاسخ HTTP برابر با ۵۰۰ INTERNAL_SERVER_ERROR خواهد بود؛
  • خط ۵۲: محتوای پاسخ روی jSON تنظیم شده است. تمام پاسخ‌های تولیدشده توسط وب‌اپلیکیشن مورد بررسی دارای سه کلید خواهند بود:
      • [action]: عملی که توسط کلاینت درخواست شده است؛
      • [état]: وضعیت برنامه پس از اجرای این اقدام؛
      • [réponse]: پاسخ وب‌سرور؛
  • خط ۵۴: پاسخ jSON به کلاینت ارسال می‌شود؛

23.9.3. آزمایش [Postman] - 1

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

Image

ما درخواست‌های مختلف را که کلاینت ما [Postman] به سرور مالیاتی ارسال می‌کند، در مجموعه‌ها گروه‌بندی خواهیم کرد.

  • در [1]، یک مجموعه جدید ایجاد کنید؛
  • در [2]، برای آن نامی انتخاب کنید؛
  • در [3]، توضیحات اختیاری است؛

Image

  • در مجموعه‌ها [4]، مجموعه‌ای به نام [impots-server-tests-version12] [5] اکنون ظاهر می‌شود؛
  • در [6]، یک پرس‌وجوی جدید می‌تواند به مجموعه اضافه شود؛

Image

  • در [7]، شما به پرس‌وجو یک نام می‌دهید؛
  • در [8]، توضیحات اختیاری است؛

Image

  • در [9-11]، پرس‌وجو به مجموعه اضافه می‌شود؛
  • در [12]، نوع پرس‌وجو را انتخاب کنید؛ در اینجا، یک پرس‌وجوی [GET]. در [19]، انواع مختلف پرس‌وجوهای موجود؛
  • در [13]، URL سرور را اینجا وارد کنید؛
  • در [14]، پارامترهای اضافه شده به URL را اینجا وارد کنید، که در نتیجه پارامترهای GET خواهند بود. مزیت قرار دادن آنها در اینجا به جای وارد کردن مستقیم در URL این است که توسط [Postman] رمزگذاری URL می‌شوند. اگر خودتان آنها را در URL وارد کنید، باید خودتان آنها را URL-encode کنید؛
  • در [15]، [Authorization] برای تعریف کاربری که وارد می‌شود استفاده می‌شود. ما نیازی به استفاده از این گزینه نخواهیم داشت؛
  • در [16]، سربرگ‌های HTTP که همراه درخواست ارسال می‌شوند. تعدادی سربرگ به‌طور خودکار در درخواست گنجانده می‌شوند. شما می‌توانید سربرگ‌های جدید را اینجا اضافه کنید؛
  • در [17]، [Body] به پارامترهای یک عملیات [POST] اشاره دارد. ما باید از این گزینه استفاده کنیم؛

ما قصد داریم تست زیر را انجام دهیم:

  • در [main.php]، مشخص می‌کنیم که فایل پیکربندی [config2.json] است، که وجود ندارد:

Image

  • خط ۱۶ کد باید از حالت توضیحی خارج شود؛
  • خط ۱۸: خطا مربوط به نام فایل پیکربندی؛

بیایید [Postman]، [13, 20] و URL را از وب‌سرور محاسبه مالیات باز کنیم و [21] را اجرا کنیم:

Image

پاسخ بازگردانده شده توسط سرور (البته لارگون باید در حال اجرا باشد) به شرح زیر است:

Image

  • در [22]، سرور کد HTTP [500 Internal Server Error] را بازگردانده است؛
  • در [23]، [Body] به بدنه پاسخ، یعنی سندی که توسط سرور پس از سربرگ‌های HTTP [28] ارسال می‌شود، اشاره دارد؛
  • در [26]، می‌بینیم که [Postman] پاسخی را از jSON دریافت کرده است؛
  • در [27]، پاسخ قالب‌بندی‌شده jSON;
  • در [28]، پاسخ خام و بدون قالب‌بندی jSON;
  • در [29]، حالت [Preview] زمانی استفاده می‌شود که پاسخ HTML باشد. سپس حالت [Preview] صفحه دریافت‌شده را نمایش می‌دهد؛
  • در [30]، پاسخ jSON سرور. این دقیقاً همان چیزی است که انتظار داشتیم؛

در [25]، سربرگ‌های HTTP ارسال‌شده در پاسخ سرور به شرح زیر هستند:

Image

  • در [32]، نوع پاسخ jSON است؛

این تست اولیه به ما نشان داد که:

  • هر نوع درخواستی را می‌توان به سرور مورد آزمایش ارسال کرد؛
  • ما می‌توانیم پارامترهای GET یا POST را تنظیم کنیم؛
  • ما پاسخ کامل را داریم: سربرگ‌های HTTP و بدنه پس از این سربرگ‌ها، [Body];

اکنون، بیایید یک تست دوم را انجام دهیم:

Image

  • به [1-3]؛ فایل [config3.json] یک فایل jSON از نظر نحوی نادرست است؛
  • در [4]، [main.php] برای استفاده از [config3.json] پیکربندی شده است؛

ما در [Postman] یک پرس‌وجوی جدید اضافه می‌کنیم:

Image

  • در [1-3]، روی [2] کلیک راست کرده و گزینه [duplicate] را برای کپی کردن پرس‌وجوی [2] انتخاب کنید؛
  • در [4]، پرس‌وجوی جدید دارای یک نام از پیش تعریف‌شده است که شما آن را به [5] تغییر می‌دهید؛

Image

  • به [6]، پرس‌وجوی نام‌گذاری‌شده؛
  • به [9-10]، همان درخواست قبلی (GET) ارسال می‌شود؛

Image

  • در [11]، پاسخ سرور jSON؛

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

23.9.4. [main.php] – ۲

اکنون به بررسی کد کنترل‌کننده اصلی [main.php] بازمی‌گردیم:


<?php

// پابندی سخت‌گیرانه به انواع اعلام‌شدهٔ پارامترهای تابع
declare (strict_types=1);

// فضای نام
namespace Application;

//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;

// مدیریت خطا توسط PHP
//ini_set("display_errors", "0");
error_reporting(E_ALL && !E_WARNING && !E_NOTICE);
//بازیابی پیکربندی
$configFilename = "config.json";

// وابستگی‌های مورد نیاز اسکریپت را شامل کنید
$rootDirectory = $config["rootDirectory"];
foreach ($config["relativeDependencies"] as $dependency) {
  require_once "$rootDirectory$dependency";
}
//وابستگی‌های مطلق (کتابخانه‌های شخص ثالث)
foreach ($config["absoluteDependencies"] as $dependency) {
  require_once "$dependency";
}

//ایجاد فایل لاگ
try {
  $logger = new Logger($config['logsFilename']);
} catch (ExceptionImpots $ex) {
  // ایجاد فایل لاگ ناموفق بود – خطای داخلی سرور
  $état = 133;
  (new JsonResponse())->send(
    NULL, NULL, $config,
    Response::HTTP_INTERNAL_SERVER_ERROR,
    ["action" => "non déterminée", "état" => $état, "réponse" => "Le fichier de logs [{$config['logsFilename']}] n'a pu être créé"],
    []);
  // تکمیل شد
  exit;
}

نظرات

  • خط ۱۸: اکنون یک فایل پیکربندی به نام [config.json] داریم که وجود دارد و از نظر نحوی صحیح است. همچنین باید بررسی کنیم که کلیدهای مورد انتظار واقعاً در این فایل موجود هستند. فرض می‌کنیم این بخشی از کار عیب‌یابی معمول توسعه‌دهنده است. می‌توانستیم همین استدلال را برای دو خطای قبلی نیز به کار ببریم؛
  • خطوط ۲۰–۲۸: ما تمام وابستگی‌های مورد نیاز برای پروژه وب را وارد می‌کنیم. ما قبلاً چندین بار با این کد مواجه شده‌ایم؛
  • خطوط ۳۱–۴۳: ما سعی می‌کنیم شیء [Logger] را ایجاد کنیم که به ما امکان می‌دهد رویدادها را در فایل [$config['logsFilename']] ثبت کنیم. این ایجاد ممکن است با شکست مواجه شود؛
  • خطوط ۳۳–۴۳: رسیدگی به خطا هنگام ایجاد شیء [Logger]؛
  • خط ۳۵: یک شماره وضعیت تنظیم می‌شود؛
  • خطوط ۳۶–۴۰: یک پاسخ jSON ارسال می‌شود؛
  • خط ۴۲: اسکریپت خاتمه می‌یابد؛

تمام پاسخ‌های ارسال‌شده به کلاینت، رابط زیر [InterfaceResponse] را پیاده‌سازی می‌کنند:

Image

کد رابط [InterfaceResponse] به شرح زیر است:


<?php

namespace Application;

// وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

interface InterfaceResponse {

  // درخواست $request: در حال پردازش
  // جلسه $session: جلسه برنامه وب
  // آرایه $config: پیکربندی برنامه
  // int statusCode: کد وضعیت پاسخ HTTP
  // آرایه $content: پاسخ سرور
  // آرایه $headers: سربرگ‌های HTTP که باید به پاسخ اضافه شوند
  // Logger $logger: لاگر برای نوشتن لاگ‌ها
  
  public function send(
    Request $request = NULL,
    Session $session = NULL,
    array $config,
    int $statusCode,
    array $content,
    array $headers,
    Logger $logger = NULL): void;
}
  • خطوط ۱۹–۲۷: رابط [InterfaceResponse] دارای یک متد واحد، [sendبرای ارسال پاسخ به کلاینت است؛
  • خطوط ۱۱–۱۷: معنای پارامترهای مختلف متد [send
  • خطوط ۲۳–۲۵: پارامترهای [$statusCode, $content, $headers] در خروجی استاندارد کنترل‌کننده‌های ثانویه برنامه گنجانده شده‌اند. با این حال، پاسخ ممکن است به اطلاعات بیشتری نیاز داشته باشد. بنابراین، این اطلاعات با سه پارامتر اول (خطوط ۲۰–۲۲) که دسترسی به تمام اطلاعات مربوط به درخواست، جلسه و پیکربندی را فراهم می‌کنند، ارائه می‌شود؛
  • خط ۲۶: پاسخ به [Logger] نیاز دارد زیرا پاسخ ارسال‌شده به کلاینت را ثبت (لاگ) خواهد کرد؛

کلاس [JsonResponse] رابط [InterfaceResponse] را به شرح زیر پیاده‌سازی می‌کند:


<?php

namespace Application;

//وابستگی‌های Symfony
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;

class JsonResponse extends ParentResponse implements InterfaceResponse {

  // درخواست $request: در حال پردازش
  // جلسه $session: جلسه برنامه وب
  // آرایه $config: پیکربندی برنامه
  // int statusCode: کد وضعیت پاسخ HTTP
  // آرایه $content: پاسخ سرور
  // آرایه $headers: سربرگ‌های HTTP که باید به پاسخ اضافه شوند
  // لاگ‌گیر $logger: لاگ‌گیر برای نوشتن لاگ‌ها

  public function send(
    Request $request = NULL,
    Session $session = NULL,
    array $config,
    int $statusCode,
    array $content,
    array $headers,
    Logger $logger = NULL): void {

    //آماده‌سازی سریالایزر Symfony
    $serializer = new Serializer(
      [
      // برای سریال‌سازی اشیاء مورد نیاز است
      new ObjectNormalizer()],
      // jSON رمزگذار
      //برای گزینه‌ها، OU را بین گزینه‌های مختلف قرار دهید
      [new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))]
    );
    //سریالی‌سازی jSON
    $json = $serializer->serialize($content, 'json');
    // سربرگ‌ها
    $headers = array_merge($headers, ["content-type" => "application/json"]);
    //ارسال پاسخ
    parent::sendResponse($statusCode, $json, $headers);
    // لاگ
    if ($logger !== NULL) {
      $logger->write("réponse=$json\n");
    }
  }

}

توضیحات

  • خط ۱۳: کلاس رابط [InterfaceResponse] را پیاده‌سازی می‌کند؛
  • خط ۱۳: کلاس از کلاس [ParentResponse] ارث می‌برد. تمام انواع [Response] از این کلاس ارث می‌برند. این کلاس والد است که پاسخ را به کلاینت ارسال می‌کند (خط ۴۶). از آنجایی که این کد برای تمام انواع [Response] مشترک بود، به یک کلاس والد استخراج شد؛
  • خطوط 33–40: نمونه‌سازی سریالایزر [Symfony]، که پاسخ را از سرور [$content] به یک رشته jSON تبدیل می‌کند (خط 42);
  • خطوط ۳۴–۳۶: اولین پارامتر سازنده [Serializer] یک آرایه است. این آرایه شامل یک نمونه از کلاس [ObjectNormalizer] است که برای سریالی‌سازی اشیاء مورد نیاز است. در این برنامه، این کار با یک لیست از شبیه‌سازی‌ها انجام می‌شود، که در آن هر شبیه‌سازی یک نمونه از کلاس [Simulation] است؛
  • خط ۳۹: پارامتر دوم سازنده [Serializer] نیز یک آرایه است: این شامل تمام انکودرهای مورد استفاده در یک سریالیزاسیون است (XML، jSON، CSV و غیره)؛
  • خط ۳۹: در اینجا تنها یک رمزگذار از نوع [JsonEncoder] وجود خواهد داشت. ممکن بود سازنده بدون پارامتر کافی باشد. در اینجا، ما پارامتر [JsonEncode] را صرفاً برای ارسال گزینه‌های رمزگذاری jSON به سازنده پاس کرده‌ایم؛
  • خط ۳۹: پارامتر سازنده [JsonEncode] یک آرایه از گزینه‌ها است. در اینجا از گزینه [JSON_UNESCAPED_UNICODE] استفاده می‌کنیم تا درخواست کنیم که کاراکترهای UTF-8 در رشته jSON به‌صورت بومی نمایش داده شوند، نه اینکه «escaped» شوند؛
  • خط ۴۲: بدنه پاسخ HTTP با استفاده از سریالایزر قبلی به jSON سریال می‌شود؛
  • خط ۴۴: ما هدر HTTP را اضافه می‌کنیم که به کلاینت می‌گوید قصد داریم jSON را برایشان ارسال کنیم؛
  • خط ۴۶: از کلاس والد می‌خواهیم پاسخ را برای کلاینت ارسال کند؛
  • خطوط ۴۸–۵۰: پاسخ jSON را ثبت می‌کنیم؛

کد کلاس والد [ParentResponse] به شرح زیر است:


<?php

namespace Application;

// وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Response;

class ParentResponse {

  // int $statusCode: کد وضعیت پاسخ HTTP
  // رشته $content: بدنه پاسخ ارسالی
  //بسته به مورد، این یک رشته است: jSON, XML, HTML
  //آرایه $headers: سربرگ‌های HTTP که باید به پاسخ اضافه شوند

  public function sendResponse(
    int $statusCode,
    string $content,
    array $headers): void {

    //آماده‌سازی پاسخ متنی سرور
    $response = new Response();
    $response->setCharset("utf-8");
    //کد وضعیت
    $response->setStatusCode($statusCode);
    // سربرگ‌ها
    foreach ($headers as $text => $value) {
      $response->headers->set($text, $value);
    }
    //ارسال پاسخ
    $response->setContent($content);
    $response->send();
  }
}

توضیحات

  • خطوط ۱۰–۱۳: معنای سه پارامتر متد [send]؛
  • خط ۱۷: توجه کنید که بدنه پاسخ از نوع [string] است و بنابراین آماده ارسال است (خط ۳۰);
  • خط ۲۲: پاسخ حاوی کاراکترهایی از نوع UTF-8 خواهد بود؛
  • خط ۲۴: کد وضعیت پاسخ HTTP;
  • خطوط ۲۶–۲۸: افزودن سربرگ‌های HTTP ارائه‌شده توسط کد فراخوانی؛
  • خطوط ۳۰–۳۱: پاسخ به کلاینت ارسال می‌شود؛

ما چرخهٔ کامل یک پاسخ jSON را تشریح کرده‌ایم. دیگر به آن باز نخواهیم گشت. صرفاً لازم است امضای رابط [InterfaceResponse] را به یاد بیاوریم:


interface InterfaceResponse {

  // درخواست $request: در حال پردازش
  // جلسه $session: جلسه برنامه وب
  // آرایه $config: پیکربندی برنامه
  // int statusCode: کد وضعیت پاسخ HTTP
  // آرایه $content: پاسخ سرور
  // آرایه $headers: سربرگ‌های HTTP که باید به پاسخ اضافه شوند
  // لاگ‌گیر $logger: لاگ‌گیر برای نوشتن لاگ‌ها
  
  public function send(
    Request $request = NULL,
    Session $session = NULL,
    array $config,
    int $statusCode,
    array $content,
    array $headers,
    Logger $logger = NULL): void;
}

کنترل‌کننده اصلی [main.php] باید هر بار که درخواست ارسال پاسخ به مشتری را می‌دهد، به این امضا پایبند باشد.

23.9.5. [Postman] تست‌ها – ۲

ما فایل [config.json] را به شرح زیر اصلاح می‌کنیم:

Image

  • در [1]، مشخص می‌کنیم که فایل لاگ [Logs] است، که پوشه‌ای به نام [2] می‌باشد. بنابراین ایجاد فایل [Logs] باید با شکست مواجه شود؛

ما یک درخواست جدید به نام [erreur-133] ایجاد می‌کنیم: [Postman] [3]:

Image

  • [2-4]: ما همان درخواست را مانند دو تست قبلی تعریف می‌کنیم؛
  • [5-7]: ما در واقع پاسخ مورد انتظار jSON را دریافت می‌کنیم؛

23.9.6. [main.php] – ۳

بیایید بررسی خود را از کنترلر اصلی [main.php] ادامه دهیم:


<?php

// پابندی سخت به انواع اعلام‌شدهٔ پارامترهای تابع
declare (strict_types=1);

// فضای نام
namespace Application;

// وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;

// مدیریت خطا توسط PHP


// ایجاد فایل لاگ


// اولین لاگ
$logger->write("\n---nouvelle requête\n");
//درخواست فعلی
$request = Request::createFromGlobals();

// جلسه
$session = new Session();
$session->start();
// فهرست خطاها
$erreurs = [];
$erreur = FALSE;
// پردازش اقدام درخواستی
if (!$request->query->has("action")) {
  $erreurs[] = "paramètre [action] manquant";
  $erreur = TRUE;
  $état = 101;
  $action = "";
} else {
  // اقدام در حال ثبت است
  $action = strtolower($request->query->get("action"));
}
//ثبت اقدام
$logger->write("action [$action] demandée\n");

//آیا اقدام وجود دارد؟
if (!$erreur && !array_key_exists($action, $config["actions"])) {
  $erreurs[] = "action [$action] invalide";
  $erreur = TRUE;
  $état = 102;
}

// نوع جلسه باید قبل از انجام برخی اقدامات مشخص باشد
if (!$erreur && !$session->has("type") && $action !== "init-session") {
  $erreurs[] = "pas de session en cours. Commencer par action [init-session]";
  $erreur = TRUE;
  $état = 103;
}

//برای انجام برخی عملیات، باید احراز هویت شوید
if (!$erreur && !$session->has("user") && $action !== "authentifier-utilisateur" && $action !== "init-session") {
  $erreurs[] = "action demandée par utilisateur non authentifié";
  $erreur = TRUE;
  $état = 104;
}

// خطاها؟
if ($erreurs) {
  // پاسخ آماده شده اما ارسال نشده است  
  $statusCode = Response::HTTP_BAD_REQUEST;
  $content = ["réponse" => $erreurs];
  $headers = [];
} else {
  // ---------------------------
  // عمل با استفاده از کنترلر آن اجرا می‌شود
  $controller = __NAMESPACE__ . $config["actions"][$action];
  $logger->write("contrôleur : $controller\n");
  list($statusCode, $état, $content, $headers) = (new $controller())->execute($config, $request, $session);
}

// --------------------- پاسخ ارسال می‌شود
// در صورت خطای مرگبار HTTP_INTERNAL_SERVER_ERROR
// در صورت امکان ایمیلی برای مدیر ارسال می‌شود
if ($statusCode === Response::HTTP_INTERNAL_SERVER_ERROR && $config['adminMail'] != NULL) {
  $infosMail = $config['adminMail'];
  $infosMail['message'] = json_encode($content, JSON_UNESCAPED_UNICODE);
  $sendAdminMail = new SendAdminMail($infosMail, $logger);
  $sendAdminMail->send();
}
// پاسخ به نوع جلسه بستگی دارد
if ($session->has("type")) {
  // نوع جلسه در داخل جلسه قرار دارد
  $type = $session->get("type");
} else {
  //اگر در جلسه نوع وجود نداشته باشد، پاسخ به‌طور پیش‌فرض در jSON خواهد بود
  $type = "json";
}
//کلیدهای [action, état] به پاسخ کنترلر اضافه می‌شوند
$content = ["action" => $action, "état" => $état] + $content;
//شیء [Response] را که مسئول ارسال پاسخ به کلاینت است، ایجاد می‌کند
$response = __NAMESPACE__ . $config["types"][$type]["response"];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);

//پاسخ ارسال شده است – منابع آزاد می‌شوند
$logger->close();
exit;

نظرات

  • پس از انجام بررسی‌های اولیه و اطمینان از قابلیت کارکرد، کنترل‌کننده اصلی توجه خود را به عملی که از آن خواسته شده انجام دهد، معطوف می‌کند: این عمل باید شرایط خاصی را برآورده سازد؛
  • خط ۲۱: ما ثبت می‌کنیم که یک درخواست جدید داریم. قبلاً نمی‌توانستیم این کار را انجام دهیم زیرا مطمئن نبودیم که فایل لاگ معتبری داریم؛
  • خط ۲۳: ما تمام اطلاعات درخواست مشتری را در شی Symfony به نام [Request] قرار می‌دهیم؛
  • خط ۲۶: ما یک جلسه جدید را آغاز می‌کنیم یا در صورت وجود، جلسه موجود را بازیابی می‌کنیم؛
  • خط ۲۷: جلسه فعال می‌شود؛
  • خط ۲۹: یک آرایه از پیام‌های خطا؛
  • خط ۳۰: یک بول که با پیشرفت تست، به ما می‌گوید که آیا خطایی رخ داده است یا خیر؛
  • خط ۳۲: پارامتر [action] باید بخشی از URL در قالب [main.php?action=uneAction] باشد. بنابراین پارامتر [action] جزئی از پارامترهای [$request→query] است؛
  • خطوط ۳۳–۳۶: حالتی که پارامتر [action] در URL وجود ندارد. خطا ثبت می‌شود و وضعیت [101] به آن اختصاص داده می‌شود؛
  • خط ۳۹: اگر پارامتر [action] در URL موجود باشد، ذخیره می‌شود؛
  • خط ۴۲: نوع اقدام ثبت می‌شود؛
  • خطوط ۴۵–۴۹: اگر پارامتر [action] موجود باشد، باید معتبر باشد. تمام عملیات مجاز در آرایهٔ asociative [$config["actions"]] تعریف شده‌اند؛
  • خطوط ۴۶–۴۸: اگر اقدام نامعتبر باشد، خطا ثبت می‌شود و وضعیت [102] به آن اختصاص داده می‌شود؛
  • خطوط ۵۲–۵۶: اقدام معتبر است. این اقدام همچنان باید شرایط بیشتری را برآورده کند. برنامه وب سه نوع پاسخ ارائه می‌دهد (jSON, XML, HTML). این نوع توسط عمل [init-session] تعیین می‌شود. این عمل نوع جلسه را در کلید [type] قرار می‌دهد؛
  • خط ۵۲: به جز اقدام [init-session]، هر اقدام دیگری باید با کلید [type] در جلسه انجام شود؛
  • خطوط ۵۳–۵۵: اگر اینطور نباشد، خطا ثبت می‌شود و وضعیت [103] به آن اختصاص داده می‌شود؛
  • خطوط ۵۸–۶۳: به جز عملیات [init-session] و [authentifier-utilisateur]، سایر عملیات باید پس از احراز هویت انجام شوند. این کار با استفاده از اقدام [authentifier-utilisateur] انجام می‌شود که در صورت موفقیت در احراز هویت، یک توکن [user] را در جلسه تنظیم می‌کند؛
  • خط ۵۹: اگر عمل نه [init-session] باشد و نه [authentifier-utilisateur] و کلید [user] در جلسه وجود نداشته باشد، آنگاه خطایی رخ می‌دهد؛
  • خطوط ۶۰–۶۲: خطا ثبت می‌شود و وضعیت [104] به آن اختصاص می‌یابد؛
  • خطوط ۶۶–۷۱: بررسی می‌کنیم که آیا آرایه [$erreurs] خالی نیست. اگر چنین باشد، آنگاه اقدام درخواست‌شده یا زمینه اجرای آن نادرست است؛
  • خطوط ۶۸–۷۰: پاسخ مورد نظر برای ارسال به کلاینت آماده می‌شود، اما هنوز ارسال نشده است؛
  • خط ۶۸: کد وضعیت HTTP;
  • خط ۶۹: بدنه پاسخ؛
  • خط ۷۰: سربرگ‌هایی که باید به پاسخ اضافه شوند؛ در اینجا هیچ‌کدام وجود ندارد؛
  • خط ۷۳: ما یک اقدام معتبر داریم. از کنترلر (ثانویه) آن خواهیم خواست تا آن را پردازش کند؛
  • خط ۷۴: ما نام کلاس کنترلر را که باید اجرا شود، می‌سازیم. [__NAMESPACE__] فضای نامی است که در آن قرار داریم؛ در اینجا، این [Application] (خط ۷) است؛
  • نام کلاس‌های کنترلر ثانویه در فایل [config.json] قرار دارد:

"actions":
            {
                "init-session": "\\InitSessionController",
                "authentifier-utilisateur": "\\AuthentifierUtilisateurController",
                "calculer-impot": "\\CalculerImpotController",
                "lister-simulations": "\\ListerSimulationsController",
                "supprimer-simulation": "\\SupprimerSimulationController",
                "fin-session": "\\FinSessionController",
                "afficher-calcul-impot": "\\AfficherCalculImpotController"
            },

هر اکشن با یک کنترل‌کننده ثانویه مطابقت دارد. اگر اکشن [authentifier-utilisateur] باشد، متغیر [$controller] در خط 74 بنابراین مقدار [Application/AuthentifierUtilisateurController] را خواهد داشت؛

  • خط ۷۵: نام کنترل‌کننده ثانویه برای تأیید در حین توسعه ثبت می‌شود؛
  • خط ۷۶: کنترل‌کننده ثانویه اجرا می‌شود. کمی بعد به کنترل‌کننده‌های ثانویه باز خواهیم گشت؛
  • خط ۷۶: تمام کنترل‌کننده‌های ثانویه همان نوع نتیجه را بازمی‌گردانند که یک آرایه است:
    • عنصر اول آرایه [$statusCodeکد وضعیت HTTP پاسخ ارسال‌شده است؛
    • عنصر دوم، [$étatوضعیت برنامه پس از اجرای کنترل‌کننده است؛
    • عنصر سوم، [$content]، یک آرایهٔ انجمنی با کلید واحد [réponse] است که بدنهٔ پاسخ ارسال‌شده به کلاینت را تشکیل می‌دهد؛
    • عنصر چهارم، [$headersآرایه‌ای از سربرگ‌ها (HTTP) است که باید به پاسخ ارسالی به کلاینت اضافه شود؛
  • خط ۷۹: به اینجا می‌رسیم:
    • یا به این دلیل که خطایی رخ داده است (خطوط 68–70);
    • یا پس از اجرای یک کنترلر (خطوط ۷۲–۷۶)؛
    • در هر دو حالت، عناصر [$statusCode, $état, $content, $headers] مورد نیاز برای تولید پاسخ به کلاینت مشخص هستند؛
  • خطوط ۸۲–۸۷: رسیدگی به مورد خاص کد وضعیت [500 Internal Server Error]. اگر یک کنترلر این کد وضعیت را تنظیم کرده باشد، به این معنی است که برنامه کاربردی نمی‌تواند کار کند. این وضعیت، برای مثال، در مورد محاسبه مالیات زمانی رخ می‌دهد که SGBD مورد استفاده راه‌اندازی نشده باشد یا دیگر پاسخگو نباشد. در این صورت، ایمیلی برای مدیر برنامه کاربردی ارسال می‌شود تا او را مطلع کنند. ما به طور خاص در مورد این کد توضیحی نمی‌دهیم. استفاده از کلاس [SendAdminMail] قبلاً توضیح داده شده است (به پاراگراف مرتبط مراجعه کنید
  • خطوط ۸۹–۹۵: نوع [jSON, XML, HTML] وب‌اپلیکیشن تعیین می‌شود. اگر اقدام [init-session] با موفقیت اجرا شده باشد، این نوع در جلسه‌ای که با کلید [type] مرتبط است، موجود است. (خط 91). اگر اینطور نباشد، آنگاه یک نوع به صورت دلخواه برای پاسخ تعیین می‌شود: نوع jSON (خط 94);
  • خط 97: [$content] یک آرایه با یک کلید واحد، [réponse]، و یک مقدار واحد—بدنه پاسخ ارسال‌شده به کلاینت—است. کلیدهای [action] و [état] به آن اضافه می‌شوند. کلید [action] ردیابی لاگ‌ها در فایل [logs.txt] را آسان‌تر می‌کند. کلید [état] دو نقش خواهد داشت:
    • این امکان را برای کلاینت‌های jSON و XML فراهم می‌کند تا وضعیت وب‌اپلیکیشن را که توسط اقدام اجرا شده ایجاد شده است، تعیین کنند؛
    • در صورت پاسخ HTML، امکان انتخاب نمای HTML برای ارسال به مرورگر مشتری را فراهم می‌کند؛
  • خط ۹۹: نوع کلاسی که باید برای ارسال پاسخ به کلاینت اجرا شود، انتخاب می‌شود؛

ما قبلاً کلاس [JsonResponse] را در بخش «link» معرفی کرده‌ایم. این کلاس رابط [InterfaceResponse] را پیاده‌سازی می‌کند و از کلاس [ParentResponse] ارث می‌برد. این موضوع در مورد دو کلاس دیگر، [XmlResponse] و [HtmlResponse] نیز صدق می‌کند.

پاسخ‌ها در پوشه [Responses] جمع‌آوری شده‌اند:

Image

تمام این کلاس‌ها رابط [InterfaceResponse] را پیاده‌سازی می‌کنند که در بخشی که اینجا به آن لینک شده نیز توضیح داده شده است:


<?php

namespace Application;

//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

interface InterfaceResponse {

  //درخواست $request: درخواست در حال پردازش است
  // جلسه $session: جلسه برنامه وب
  // آرایه $config: پیکربندی برنامه
  // int statusCode: کد وضعیت پاسخ HTTP
  // آرایه $content: پاسخ سرور
  // آرایه $headers: سربرگ‌های HTTP که باید به پاسخ اضافه شوند
  // لاگ‌گیر $logger: لاگ‌گیر برای نوشتن لاگ‌ها
  
  public function send(
    Request $request = NULL,
    Session $session = NULL,
    array $config,
    int $statusCode,
    array $content,
    array $headers,
    Logger $logger = NULL): void;
}

این رابط یک متد واحد به نام [send] دارد که مسئول ارسال پاسخ به کلاینت است. این متد دارای هفت پارامتر است که در خطوط ۱۱ تا ۱۷ توصیف شده‌اند. تمام کلاس‌ها و رابط‌ها در پوشه [Responses] در فضای نام [Application] (خط ۳) قرار دارند.

بیایید به کد [main.php] بازگردیم:



//کلیدهای [action, état] به پاسخ کنترلر اضافه می‌شوند
$content = ["action" => $action, "état" => $état] + $content;
// شیء [Response] برای ارسال پاسخ به کلاینت ایجاد می‌شود
$response = __NAMESPACE__ . $config["types"][$type];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);

//پاسخ ارسال شده است – منابع آزاد می‌شوند
$logger->close();
exit;
  • خط ۵: کلاس [Response] که برای نوع برنامه مناسب است، نمونه سازی می‌شود. این کلاس‌ها در فایل [config.json] به صورت زیر تعریف شده‌اند:

"types": {
        "json": "\\JsonResponse",
        "html": "\\HtmlResponse",
        "xml": "\\XmlResponse"
    },
  • خط ۵: نام کلاس با فضای نام مربوطه پیش‌وند می‌شود؛
  • خط ۶: کلاس [Response] نمونه برداری شده و متد [send] آن با ۷ پارامتر مورد انتظار فراخوانی می‌شود. این پارامترها متعلق به رابط [InterfaceResponse] هستند که همه کلاس‌های پاسخ آن را پیاده‌سازی می‌کنند. این کار پاسخ را به کلاینت ارسال می‌کند؛
  • خط ۹: فایل لاگ بسته می‌شود؛
  • خط ۱۰: کنترل‌کننده اصلی کار خود را به پایان رسانده است؛

23.9.7. آزمایش‌های [Postman] – ۳

ما سناریوهای خطای مختلف را برای پارامتر [action] از URL آزمایش خواهیم کرد.

Image

  • در [1]:
    • [erreur-101]: حالتی که پارامتر [action] از URL حذف شده است؛
    • [erreur-102]: حالتی که پارامتر [action] در URL موجود است اما شناسایی نمی‌شود؛
    • [erreur-103]: پارامتر [action] در URL موجود است؛ شناسایی شده است، اما نوع پاسخ مورد انتظار [json, xml, html] تعریف نشده است؛

هر پرس‌وجو اجرا می‌شود. نتایج به‌دست‌آمده را مستقیماً ارائه می‌دهیم:

بالا:

  • در [2-4]، یک پرس‌وجو بدون پارامتر [action] در URL [4];
  • در [5-7]، نتیجه jSON;

Image

بالا:

  • در [5-9]، درخواستی با پارامتر [action] نامعتبر؛
  • در [10-13]، پاسخ jSON;

Image

بالا:

  • در [14-19]، عملی شناسایی شده اما نوع (json, xml, html) هنوز مشخص نشده است؛
  • در [20-23]، پاسخ سرور jSON;

23.10. کنترل‌کننده‌های ثانویه

هر اکشن توسط یکی از کنترلرها در پوشه [Controllers] اجرا می‌شود:

Image

Image

در معماری کلی برنامهٔ فوق، کنترل‌کننده‌های ثانویه در [2a] قرار دارند.

هر کنترل‌کننده رابط زیر [InterfaceController] را پیاده‌سازی می‌کند:


<?php

namespace Application;

//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

interface InterfaceController {

  //$config پیکربندی برنامه است
  // پردازش یک درخواست
  //از Session استفاده می‌کند و می‌تواند آن را تغییر دهد
  // QZXW2HTMLCJG جزئیات اضافی مختص هر کنترل‌کننده هستند
  
  //آرایه‌ای را بازمی‌گرداند [$statusCode, $état, $content, $headers]
  public function execute(
    array $config,
    Request $request,
    Session $session,
    array $infos=NULL): array;
}

نظرات

  • تمام کنترل‌کننده‌های ثانویه از طریق متد [execute] در خط 17 اجرا می‌شوند. اطلاعات شناخته‌شده از کنترل‌کننده اصلی به این متد ارسال می‌شود:
    • خط ۱۸: [array $configکه پیکربندی برنامه را در بر می‌گیرد؛
    • خط ۱۹: [Request $request]، که درخواست HTTP در حال پردازش است؛
    • خط ۲۰: [Session $sessionکه جلسهٔ فعلی وب‌اپلیکیشن است؛
    • خط ۲۱: [array $infos=NULL]، که یک آرایه اطلاعاتی اضافی برای کنترل‌کننده در صورتی است که سه پارامتر اول متد کافی نباشند. در این برنامه، این پارامتر هرگز استفاده نشده است. این پارامتر به عنوان یک اقدام احتیاطی گنجانده شده است؛
  • خط ۲۱: متد [execute] آرایه [$statusCode, $état, $content, $headers] را برمی‌گرداند
    • [int $statusCode]: کد وضعیت پاسخ به HTTP;
    • [int $état]: وضعیت برنامه در پایان اجرا؛
    • [array $content]: یک آرایهٔ asociative [réponse=>résultat] که در آن [résultat] می‌تواند از هر نوعی باشد: این نتیجه‌ای است که توسط کنترلر تولید می‌شود و پس از سریال شدن به صورت یک رشتهٔ کاراکتری، به کلاینت ارسال خواهد شد؛
    • [array $headers]: فهرست سربرگ‌های HTTP که باید در پاسخ سرور HTTP گنجانده شوند؛

هر کنترل‌کنندهٔ ثانویه توسط کد زیر از کنترل‌کنندهٔ اصلی فراخوانی می‌شود:


// عمل با استفاده از کنترلر آن اجرا می‌شود
 $controller = __NAMESPACE__ . $config["actions"][$action];
 list($statusCode, $état, $content, $headers) = (new $controller())->execute($config, $request, $session);

در خط ۳، می‌بینیم که پارامتر چهارم، [array $infos=NULL از متد [execute] استفاده نمی‌شود.

23.11. اقدامات

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

اقدام
نقش
زمینهٔ اجرا
init-session
برای مشخص کردن نوع پاسخ‌های مورد نظر (json، xml، html) استفاده می‌شود
درخواست GET main.php؟action=init-session&type=x
ممکن است در هر زمان صادر شود
تأیید هویت کاربر
ورود کاربر را مجاز یا رد می‌کند
درخواست POST main.php?action=authenticate-user
درخواست باید دارای دو پارامتر POST [user, password] باشد
فقط در صورتی قابل ارسال است که نوع جلسه (json, xml, html) مشخص باشد
محاسبه-مالیات
شبیه‌سازی محاسبه مالیات را انجام می‌دهد
درخواست POST main.php?action=calculate-tax
درخواست باید سه پارامتر POST داشته باشد: [marié, enfants, salaire]
فقط در صورتی قابل اجرا است که نوع جلسه (json، xml، html) مشخص باشد و کاربر احراز هویت شده باشد
فهرست-شبیه‌سازی‌ها
درخواست فهرستی از شبیه‌سازی‌های انجام‌شده از ابتدای جلسه
درخواست GET main.php?action=list-simulations
این درخواست هیچ پارامتر دیگری را نمی‌پذیرد
فقط در صورتی قابل اجرا است که نوع جلسه (json، xml، html) مشخص باشد و کاربر احراز هویت شده باشد
حذف-شبیه‌سازی
حذف یک شبیه‌سازی از فهرست شبیه‌سازی‌ها
درخواست GET main.php?action=list-simulations&number=x
درخواست هیچ پارامتر دیگری را نمی‌پذیرد
فقط در صورتی قابل اجرا است که نوع جلسه (json, xml, html) مشخص باشد و کاربر احراز هویت شده باشد
پایان جلسه
پایان جلسه شبیه‌سازی.
از نظر فنی، جلسه وب قدیمی حذف شده و یک جلسه جدید ایجاد می‌شود
فقط در صورتی قابل اجرا است که نوع جلسه (json، xml، html) مشخص باشد و کاربر احراز هویت شده باشد

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

  • آنها پارامترهای خود را بررسی می‌کنند. این‌ها در شیء [Request→query] برای پارامترهای موجود در URL، و در شیء [Request→request] برای آن‌هایی که ارسال شده‌اند (درخواست POST) یافت می‌شوند؛
  • یک کنترل‌کننده مشابه یک تابع یا متد است که اعتبار پارامترهایش را بررسی می‌کند. با این حال، برای کنترل‌کننده کمی پیچیده‌تر است:
    • ممکن است پارامترهای مورد انتظار وجود نداشته باشند؛
    • پارامترهای مورد انتظار همگی رشته‌ها هستند، در حالی که یک تابع می‌تواند نوع پارامترهای خود را مشخص کند. اگر پارامتر مورد انتظار یک عدد باشد، باید بررسی شود که رشته پارامتر واقعاً متعلق به یک عدد است؛
    • پس از آنکه تأیید شد که پارامترهای مورد انتظار موجود و از نظر دستوری صحیح هستند، باید بررسی شود که آیا آنها در زمینه اجرای فعلی معتبر هستند یا خیر. این زمینه در جلسه (session) موجود است. مثال احراز هویت نمونه‌ای از یک زمینه اجرایی است. برخی اقدامات باید فقط پس از احراز هویت مشتری پردازش شوند. به طور کلی، یک کلید در جلسه نشان می‌دهد که آیا این احراز هویت انجام شده است یا خیر؛
    • پس از انجام بررسی‌های پیشین، کنترل‌کننده ثانویه می‌تواند کار خود را ادامه دهد. این فرآیند تأیید پارامترها بسیار مهم است. ما نمی‌توانیم در هیچ نقطه‌ای از چرخه عمر برنامه، هر چیزی را که مشتری ارسال می‌کند، بپذیریم. ما باید کنترل کامل چرخه عمر برنامه را حفظ کنیم؛
    • پس از اتمام کار، کنترل‌کننده ثانویه آرایه [$statusCode, $état, $content, $headers] را که کنترل‌کننده اصلی فراخوانی‌کننده آن را انتظار دارد، بازمی‌گرداند؛

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

23.11.1. اقدام [init-session]

اقدام [init-session] توسط کنترلر زیر، [InitSessionController]، پردازش می‌شود:


<?php

namespace Application;

//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;

class InitSessionController implements InterfaceController {

  // $config پیکربندی برنامه است
  // پردازش یک درخواست
  //از Session استفاده می‌کند و می‌تواند آن را تغییر دهد
  //$infos جزئیات اضافی مختص هر کنترلر هستند
  
  //یک آرایه را بازمی‌گرداند [$statusCode, $état, $content, $headers]
  public function execute(
    array $config,
    Request $request,
    Session $session,
    array $infos = NULL): array {

    //نیازمند یک GET و یک پارامتر واحد غیر از [action] است
    $method = strtolower($request->getMethod());
    $erreur = $method !== "get" || $request->query->count() != 2;
    if ($erreur) {
      $état = 701;
      $message = "méthode GET exigée avec paramètres [action, type] dans l'URL";
      return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
    }
    // ما پارامترها را از GET بازیابی می‌کنیم
    $erreur = FALSE;
    // نوع
    if (!$request->query->has("type")) {
      $erreur = TRUE;
      $état = 702;
      $message = "paramètre [type] manquant";
    } else {
      $type = strtolower($request->query->get("type"));
    }
    // تأیید نوع
    if (!$erreur && !array_key_exists($type, $config["types"])) {
      $erreur = TRUE;
      $état = 703;
      $message = "paramètre type [$type] invalide";
    }
    //خطا؟
    if ($erreur) {
      return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
    }
    // نوع جلسه را در جلسه تنظیم کنید
    $session->set("type", $type);
    // پیام موفقیت
    $message = "session démarrée avec type [$type]";
    $état = 700;
    return [Response::HTTP_OK, $état, ["réponse" => $message], []];
  }

}

نظرات

  • در انتظار یک درخواست [GET main.php?action=init-session&type=xxx] هستیم
  • خطوط ۲۵–۲۶: بررسی می‌کنیم که درخواست یک درخواست GET با دو پارامتر در URL است؛
  • خطوط ۲۷–۳۱: اگر اینطور نباشد، خطا ثبت می‌شود و یک نتیجه [$statusCode, $état, $content, $headers] به کنترلر اصلی ارسال می‌شود؛
  • خطوط ۳۵–۳۹: بررسی می‌کنیم که پارامتر [type] در URL موجود باشد. اگر این‌طور نباشد، خطا را ثبت می‌کنیم؛
  • خط ۴۰: نوع جلسه ثبت می‌شود؛
  • خطوط ۴۳–۴۷: بررسی می‌کنیم که نوع جلسه یکی از موارد زیر باشد (json, xml, html). اگر اینطور نباشد، خطا را ثبت می‌کنیم؛
  • خطوط ۴۹–۵۱: اگر خطایی رخ داده باشد، نتیجه [$statusCode, $état, $content, $headers] به کنترل‌کننده اصلی ارسال می‌شود؛
  • خط ۵۳: نوع جلسه در جلسهٔ وب‌اپلیکیشن ذخیره می‌شود؛
  • خطوط ۵۵–۵۷: کنترل‌کننده وظیفه خود را به پایان رسانده است. یک نتیجه موفق ([$statusCode, $état, $content, $headers]) به کنترل‌کننده اصلی ارسال می‌شود؛

بیایید به یاد بیاوریم که کنترلر اصلی با پاسخ‌های کنترلرهای ثانویه چه می‌کند:


// خطاها؟
if ($erreurs) {
  // آماده‌سازی پاسخ بدون ارسال آن  
  $statusCode = Response::HTTP_BAD_REQUEST;
  $content = ["réponse" => $erreurs];
  $headers = [];
} else {
  // ---------------------------
  // اجرای اقدام با استفاده از کنترلر آن
  $controller = __NAMESPACE__ . $config["actions"][$action];
  $logger->write("contrôleur : $controller\n");
  list($statusCode, $état, $content, $headers) = (new $controller())->execute($config, $request, $session);
}

// --------------------- ارسال پاسخ
//در صورت رخ دادن خطای فاجعه‌بار HTTP_INTERNAL_SERVER_ERROR
// در صورت امکان یک ایمیل برای مدیر ارسال می‌شود
if ($statusCode === Response::HTTP_INTERNAL_SERVER_ERROR && $config['adminMail'] != NULL) {
  $infosMail = $config['adminMail'];
  $infosMail['message'] = json_encode($content, JSON_UNESCAPED_UNICODE);
  $sendAdminMail = new SendAdminMail($infosMail, $logger);
  $sendAdminMail->send();
}
// پاسخ به نوع جلسه بستگی دارد
if ($session->has("type")) {
  // نوع جلسه در داخل جلسه قرار دارد
  $type = $session->get("type");
} else {
  //اگر در جلسه نوع وجود نداشته باشد، پاسخ به‌طور پیش‌فرض در jSON خواهد بود
  $type = "json";
}
//کلیدهای [action, état] به پاسخ کنترلر اضافه می‌شوند
$content = ["action" => $action, "état" => $état] + $content;
//شیء [Response] را که مسئول ارسال پاسخ به کلاینت است، ایجاد می‌کند
$response = __NAMESPACE__ . $config["types"][$type]["response"];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);

//پاسخ ارسال شده است – منابع آزاد می‌شوند
$logger->close();
exit;
  • خط ۱۲: کنترل‌کننده اصلی نتیجه را از کنترل‌کننده ثانویه بازیابی می‌کند؛
  • خطوط ۳۵–۳۶: پس از انجام چند بررسی، پاسخ را با نمونه‌سازی یکی از کلاس‌های [JsonResponse, XmlResponse, HtmlResponse] بسته به نوع (json، xml، html) جلسهٔ جاری ارسال می‌کند؛

در بخش بعدی، ما آزمایش‌هایی را روی [Postman] به‌عنوان بخشی از یک سری شبیه‌سازی‌ها با استفاده از نوع [json] انجام خواهیم داد. عملکرد کلاس [JsonResponse] در بخش مرتبط توضیح داده شده است.

23.11.2. آزمایش‌های [Postman]

Image

بالا:

  • در [2]، سه تست جدید؛
  • در [3-7]، عمل [init-session] با پارامتر [type] مفقود است؛
  • در [8-11]، پاسخ سرور jSON؛

Image

بالا:

  • در [1-7]، اقدام [init-session] با پارامتر نادرست [type];
  • در [8-11]، پاسخ سرور jSON;

Image

بالا:

  • در [1-8]، اقدام [init-session] با نوع jSON;
  • در [9-12]، پاسخ سرور jSON;

23.11.3. عمل [authentifier-utilisateur]

عمل [authentifier-utilisateur] توسط کنترلر زیر [AuthentifierUtilisateurController] اجرا می‌شود:


<?php

namespace Application;

//وابستگی‌های Symfony
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;

class AuthentifierUtilisateurController implements InterfaceController {

  //$config پیکربندی برنامه است
  // پردازش یک درخواست
  //از Session استفاده می‌کند و می‌تواند آن را تغییر دهد
  // $infos جزئیات اضافی مختص هر کنترلر هستند
  // یک آرایه را بازمی‌گرداند [$statusCode, $état, $content, $headers]
  public function execute(
    array $config,
    Request $request,
    Session $session,
    array $infos = NULL): array {

    //نیازمند یک POST و یک پارامتر واحد GET است
    $method = strtolower($request->getMethod());
    $erreur = $method !== "post" || $request->query->count() != 1;
    if ($erreur) {
      $état = 201;
      $message = "méthode POST requise, paramètre [action] dans l'URL, paramètres postés [user,password]";
      //نتیجه به کنترلر اصلی بازگردانده می‌شود
      return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
    }
    // ما پارامترها را از POST بازیابی می‌کنیم
    $erreurs = [];
    //کاربر
    $état = 210;
    if (!$request->request->has("user")) {
      $état += 2;
      $erreurs[] = "paramètre [user] manquant";
    } else {
      $user = $request->request->get("user");
    }
    // رمز عبور
    if (!$request->request->has("password")) {
      $état += 4;
      $erreurs[] = "paramètre [password] manquant";
    } else {
      $password = trim($request->request->get("password"));
    }
    // خطا؟
    if ($erreurs) {
      // نتیجه به کنترلر اصلی بازگردانده می‌شود
      return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $erreurs], []];
    }
    // تأیید اعتبارنامه‌های کاربر
    //آیا کاربر وجود دارد؟
    $users = $config["users"];
    $i = 0;
    $trouvé = FALSE;
    while (!$trouvé && $i < count($users)) {
      $trouvé = ($user === $users[$i]["login"] && $users[$i]["passwd"] === $password);
      $i++;
    }
    // یافت شد؟
    if (!$trouvé) {
      // پیام خطا
      $message = "Echec de l'authentification [$user, $password]";
      $état = 221;
      // بازگرداندن نتیجه به کنترلر اصلی
      return [Response::HTTP_UNAUTHORIZED, $état, ["réponse" => $message], []];
    } else {
      //ثبت در جلسه که کاربر احراز هویت شده است
      $session->set("user", TRUE);
      // پیام موفقیت
      $message = "Authentification réussie [$user, $password]";
      $état = 200;
      // بازگرداندن نتیجه به کنترلر اصلی
      return [Response::HTTP_OK, $état, ["réponse" => $message], []];
    }
  }

}

نظرات

  • یک درخواست [POST main.php?action=authentifier-utilisateur] با دو پارامتر ارسال‌شده [user, password] انتظار می‌رود؛
  • خطوط ۲۴–۲۵: بررسی می‌کنیم که یک درخواست POST با یک پارامتر در URL داریم؛
  • خطوط ۲۶–۳۱: اگر خطایی وجود داشته باشد، ثبت می‌شود و یک نتیجه [$statusCode, $état, $content, $headers] به کنترلر اصلی بازگردانده می‌شود؛
  • خطوط ۳۶–۳۹: بررسی حضور پارامتر [user] در مقادیر ارسال‌شده. اگر موجود نباشد، خطا را ثبت می‌کنیم؛
  • خطوط ۴۳–۴۵: بررسی می‌کنیم که آیا پارامتر [password] در مقادیر ارسال‌شده وجود دارد یا خیر. اگر وجود نداشته باشد، خطا را ثبت می‌کنیم؛
  • خطوط ۵۰–۵۳: اگر هر یک از مقادیر ارسال‌شده وجود نداشته باشند، یک نتیجه [$statusCode, $état, $content, $headers] به کنترلر اصلی بازگردانده می‌شود؛
  • خطوط ۵۶–۶۲: سیستم بررسی می‌کند که جفت بازیابی‌شده [$user,$password] در آرایه [$config[‘users’]] در فایل پیکربندی موجود باشد؛
  • خطوط ۶۴–۶۹: اگر اینطور نباشد، خطا ثبت می‌شود. کد وضعیت HTTP روی [Response::HTTP_UNAUTHORIZED] تنظیم می‌شود و نتیجه [$statusCode, $état, $content, $headers] به کنترلر اصلی بازگردانده می‌شود؛
  • خط ۷۲: احراز هویت با موفقیت انجام شده است. این موضوع با تنظیم کلید [user] در جلسه ثبت می‌شود. وجود این کلید نشان‌دهنده احراز هویت موفق است؛
  • خطوط ۷۳–۷۷: یک نتیجه موفقیت‌آمیز، [$statusCode, $état, $content, $headersبه کنترلر اصلی بازگردانده می‌شود؛

23.11.4. آزمایش [Postman]

ما در حال انجام آزمایش‌های [Postman] روی کنترلر [AuthentifierUtilisateurController] در حالت jSON هستیم؛

Image

بالا:

  • در [1-6]، اقدام [authentifier-utilisateur] با GET [2]، در حالی که یک POST مورد نیاز است؛
  • در [7-10]، پاسخ سرور jSON؛

بیایید GET را با POST [2] جایگزین کنیم، بدون اینکه هیچ پارامتری را در متن پاسخ [7] درج کنیم:

Image

بالا:

  • در [1-7]، POST بدون پارامترها که در [7] ارسال شده است؛
  • در [8-11]، پاسخ سرور jSON؛

حال بیایید یک پارامتر [password] به بدنه (body) [4] درخواست اضافه کنیم:

Image

بالا:

  • در [1-6]، یک درخواست POST [2] با پارامتر [password] به [4-6] ارسال شده است. پارامترهای ارسال‌شده باید به بدنه درخواست [4] اضافه شوند. چندین روش برای ارسال مقادیر به سرور وجود دارد. ما روش [x-www-form-urlencoded] [5] را انتخاب کرده‌ایم؛
  • در [8-10]، پاسخ سرور jSON؛

اکنون پارامتر [user] را بدون پارامتر [password] تعریف کنیم:

Image

بالا:

  • در [1-7]، یک درخواست POST بدون پارامتر [password] [4-7];
  • در [8-11]، پاسخ سرور jSON;

اکنون بیایید دو پارامتر ارسال‌شده [user, password] را با مقادیری که باعث شکست احراز هویت می‌شوند، تعریف کنیم:

Image

بالا:

  • در [1-9]، یک درخواست POST با پارامترهای POST نادرست [user, password];
  • در [10-13]، پاسخ سرور jSON. به کد وضعیت [401 Unauthorized] [10] در پاسخ توجه کنید؛

اکنون یک درخواست POST با اعتبارنامه‌های معتبر:

Image

بالا:

  • در [1-9]، درخواست POST [2] با اعتبارنامه‌های معتبر [6-9];
  • در [10-13]، پاسخ سرور jSON. توجه کنید به کد وضعیت HTTP [200 OK] در [10];

23.11.5. اقدام [calculer-impot]

اقدام [calculer-impot] توسط کنترل‌کنندهٔ زیر، [CalculerImpotController]، پردازش می‌شود:


<?php

namespace Application;

//وابستگی‌های Symfony
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
//نام مستعار برای لایه [dao]
use \Application\ServerDaoWithSession as ServerDaoWithRedis;

class CalculerImpotController implements InterfaceController {

  //$config پیکربندی برنامه است
  //پردازش یک درخواست
  //از Session استفاده می‌کند و می‌تواند آن را تغییر دهد
  //$infos جزئیات اضافی مختص هر کنترل‌کننده هستند
  //یک آرایه را بازمی‌گرداند [$statusCode, $état, $content, $headers]
  public function execute(
    array $config,
    Request $request,
    Session $session,
    array $infos = NULL): array {

    //باید یک پارامتر GET و سه پارامتر POST وجود داشته باشد
    $method = strtolower($request->getMethod());
    $erreur = $method !== "post" || $request->query->count() != 1;
    if ($erreur) {
      //خطا ثبت شد
      $message = "il faut utiliser la méthode [post] avec [action] dans l'URL et les paramètres postés [marié, enfants, salaire]";
      $état = 301;
      // نتیجه را به کنترل‌کننده اصلی بازمی‌گرداند
      return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
    }
    // پارامترها را از POST بازیابی کنید
    $erreurs = [];
    $état = 310;
    //وضعیت تأهل
    if (!$request->request->has("marié")) {
      $état += 2;
      $erreurs[] = "paramètre [marié] manquant";
    } else {
      $marié = trim(strtolower($request->request->get("marié")));
      $erreur = $marié !== "oui" && $marié !== "non";
      if ($erreur) {
        $état += 4;
        $erreurs[] = "valeur [$marié] invalide pour le paramètre [marié]";
      }
    }
    // بازیابی تعداد فرزندان
    if (!$request->request->has("enfants")) {
      $état += 8;
      $erreurs[] = "paramètre [enfants] manquant";
    } else {
      $enfants = trim($request->request->get("enfants"));
      $erreur = !preg_match("/^\d+$/", $enfants);
      if ($erreur) {
        $état += 9;
        $erreurs[] = "valeur [$enfants] invalide pour le paramètre [enfants]";
      }
    }
    // استخراج حقوق سالانه
    if (!$request->request->has("salaire")) {
      $erreurs[] = "paramètre [salaire] manquant";
      $état += 16;
    } else {
      $salaire = trim($request->request->get("salaire"));
      $erreur = !preg_match("/^\d+$/", $salaire);
      if ($erreur) {
        $état += 17;
        $erreurs[] = "valeur [$salaire] invalide pour le paramètre [salaire]";
      }
    }
    //خطا؟
    if ($erreurs) {
      // نتیجه را به کنترل‌کننده اصلی بازگردانید
      return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $erreurs], []];
    }

    // ما همه چیز لازم برای ادامه را داریم
    // ردیس
    \Predis\Autoloader::register();
    try {
      // کلاینت [predis]
      $redis = new \Predis\Client();
      //ما به سرور متصل می‌شویم تا ببینیم آیا آنجاست
      $redis->connect();
    } catch (\Predis\Connection\ConnectionException $ex) {
      // موفق نشد
      // نتیجه را همراه با خطا به کنترلر اصلی بازمی‌گرداند
      $état = 350;
      return [Response::HTTP_INTERNAL_SERVER_ERROR, $état,
        ["réponse" => "[redis], " . utf8_encode($ex->getMessage())], []];
    }

    //ما پارامترهای معتبری داریم
    //ایجاد لایه [dao]
    if (!$redis->get("taxAdminData")) {
      try {
        //بازیابی داده‌های مالیاتی از پایگاه داده
        $dao = new ServerDaoWithRedis($config["databaseFilename"], NULL);
        //ذخیرهٔ داده‌های بازیابی‌شده در Redis
        $redis->set("taxAdminData", $dao->getTaxAdminData());
      } catch (\RuntimeException $ex) {
        // چیزی اشتباه پیش رفت
        //بازگرداندن نتیجه همراه با خطا به کنترلر اصلی
        $état = 340;
        return [Response::HTTP_INTERNAL_SERVER_ERROR, $état,
          ["réponse" => utf8_encode($ex->getMessage())], []];
      }
    } else {
      //داده‌های مالیاتی در حافظه دامنه ذخیره می‌شوند [application]
      $arrayOfAttributes = \json_decode($redis->get("taxAdminData"), true);
      $taxAdminData = (new TaxAdminData())->setFromArrayOfAttributes($arrayOfAttributes);
      // مصداق‌سازی لایه [dao]
      $dao = new ServerDaoWithRedis(NULL, $taxAdminData);
    }
    // ایجاد لایه [métier]
    $métier = new ServerMetier($dao);

    // ما همه چیز لازم برای کار را داریم – محاسبه مالیات
    $résultat = $métier->calculerImpot($marié, (int) $enfants, (int) $salaire);
    // شبیه‌سازی‌ای را که به‌تازگی انجام شده به جلسه اضافه کنید
    $simulation = new Simulation();
    $résultat = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $résultat;
    $simulation->setFromArrayOfAttributes($résultat);
    //آیا فهرستی از شبیه‌سازی‌ها در جلسه وجود دارد؟
    if (!$session->has("simulations")) {
      $simulations = [];
    } else {
      $simulations = $session->get("simulations");
    }
    // شبیه‌سازی را به فهرست شبیه‌سازی‌ها اضافه کنید
    $simulations[] = $simulation;
    // شبیه‌سازی‌ها را به جلسه بازگردانید
    $session->set("simulations", $simulations);
    // نتیجه را به کنترلر اصلی بازگردانید
    $état = 300;
    return [Response::HTTP_OK, $état, ["réponse" => $résultat], []];
  }

}

نظرات

  • درخواست مورد انتظار [POST main.php?action=calculer-impot] با سه پارامتر ارسال شده به [marié, enfants, salaire] است:
    • [marié] باید مقدار خود را در [oui, non] تنظیم کند؛
    • [enfants, salaire] باید عدد صحیح مثبت یا صفر باشد؛
  • خطوط ۲۶–۲۷: یک بررسی انجام می‌شود تا اطمینان حاصل شود که واقعاً یک POST با یک پارامتر واحد در URL وجود دارد؛
  • خطوط ۲۸–۳۴: اگر اینطور نباشد، یک پیام خطا به کنترل‌کننده اصلی ارسال می‌شود؛
  • خط ۳۶: پیام‌های خطا در آرایه [$erreurs] انباشته می‌شوند؛
  • خطوط ۳۹–۴۱: ما وجود پارامتر [marié] را بررسی می‌کنیم. اگر این پارامتر موجود نباشد، خطا ثبت می‌شود؛
  • خطوط ۴۳–۴۹: بررسی می‌کنیم که آیا مقدار [marié] در [oui, non] موجود است. اگر اینطور نباشد، خطا ثبت می‌شود؛
  • خطوط ۵۱–۵۴: سیستم وجود پارامتر [enfants] را بررسی می‌کند. اگر این پارامتر موجود نباشد، یک خطا ثبت می‌شود؛
  • خطوط ۵۵–۶۱: سیستم بررسی می‌کند که آیا مقدار پارامتر [enfants] یک عدد مثبت یا صفر است. اگر این‌طور نباشد، خطا ثبت می‌شود؛
  • خطوط ۶۳–۶۶: سیستم وجود پارامتر [salaire] را بررسی می‌کند. اگر این پارامتر وجود نداشته باشد، یک خطا ثبت می‌شود؛
  • خطوط ۶۷–۷۲: سیستم بررسی می‌کند که آیا مقدار پارامتر [salaire] یک عدد مثبت یا صفر است. در غیر این صورت، یک خطا ثبت می‌شود؛
  • خطوط ۷۵–۷۸: اگر آرایه [$erreurs] خالی نباشد، این نشان‌دهنده وقوع خطاها است. آرایه خطا در پاسخ گنجانده شده و نتیجه به کنترلر اصلی بازگردانده می‌شود؛
  • خط ۸۰: پارامترها معتبر هستند. مالیات قابل محاسبه است. برای این کار، لایه‌های [dao] و [métier] که قادر به انجام این محاسبه هستند، باید ساخته شوند؛
  • خطوط ۸۲–۹۴: ما یک کلاینت [Redis] ایجاد می‌کنیم؛
  • خطوط ۸۸–۹۴: اگر نتوانستیم به سرور [Redis] متصل شویم، یک کد [500 Internal Server Error] را برای کلاینت ارسال می‌کنیم؛
  • خط ۹۸: بررسی می‌کنیم که آیا سرور [Redis] کلید [taxAdminData] را دارد یا خیر. این کلید نمایانگر داده‌های مرجع مالیاتی است. اگر این کلید موجود نباشد، آنگاه داده‌های مالیاتی باید از پایگاه داده بازیابی شوند؛
  • خط ۱۰۱: لایه [dao] زمانی که نیاز به بازیابی داده‌های مالیاتی از پایگاه داده باشد، ساخته می‌شود. کلاس [ServerDaoWithRedis] در بخش «لینک» توضیح داده شده است؛
  • خط ۱۰۳: داده‌های استخراج‌شده از پایگاه داده با کلید [taxAdminData] در حافظه [Redis] ذخیره می‌شوند؛
  • خطوط ۱۰۴–۱۱۰: اگر پرس‌وجوی پایگاه داده ناموفق باشد، خطایی که توسط لایه [dao] بازگردانده می‌شود، ثبت شده و در نتیجه بازگردانده شده به کنترلر اصلی گنجانده می‌شود؛
  • خط ۱۰۹: پیام خطا که توسط لایه [PDO] بازگردانده می‌شود، به صورت [iso-8859-1] رمزگذاری می‌شود. این پیام به صورت [utf-8] رمزگذاری می‌شود؛
  • خطوط ۱۱۱–۱۱۷: اگر کلید [taxAdminData] در حافظه [Redis] وجود داشته باشد، آنگاه داده‌های مالیاتی مستقیماً به سازنده لایه [dao] ارسال می‌شود؛
  • خط ۱۱۹: لایه [métier] ایجاد می‌شود. کلاس [ServerMetier] در بخش «لینک» توضیح داده شده بود؛
  • خطوط ۱۲۴–۱۲۶: پس از محاسبه مبلغ مالیات، یک شیء [Simulation] ایجاد می‌شود. کلاس [Simulation] داده‌های یک شبیه‌سازی را در بر می‌گیرد و در بخش «Link» توصیف شده است؛
  • خطوط ۱۲۸–۱۳۲: شبیه‌سازی که به‌تازگی ایجاد شده است باید به فهرست شبیه‌سازی‌هایی که قبلاً محاسبه شده‌اند اضافه شود. این فهرست در جلسه ذخیره می‌شود، مگر اینکه هنوز هیچ شبیه‌سازی‌ای انجام نشده باشد؛
  • خطوط 133–136: شبیه‌سازی به فهرست شبیه‌سازی‌ها اضافه می‌شود و این فهرست به جلسه بازگردانده می‌شود؛
  • خطوط ۱۳۷–۱۳۹: نتیجه به کنترل‌کننده اصلی بازگردانده می‌شود؛

23.11.6. آزمایش‌های [Postman]

ما در حال اجرای تست‌های [Postman] بر روی کنترلر [CalculerImpotController] در حالت jSON هستیم؛

Image

بالا:

  • در [1-7]، یک درخواست [GET] به جای [POST] ارسال می‌شود؛
  • در [8-11]، پاسخ سرور jSON است؛

اکنون، بیایید از متد [POST]، با یا بدون پارامترهای ارسال‌شده، و همچنین با پارامترهای ارسال‌شده نامعتبر استفاده کنیم:

Image

بالا:

  • ما یک درخواست [POST] [2] با پارامترهای POST نامعتبر [6-11] [marié, enfants, salaire] انجام می‌دهیم. شما می‌توانید با برداشتن تیک مربوط به آن در [16]، ارسال یکی از این پارامترها را انتخاب نکنید. این کار به شما امکان می‌دهد سناریوهای مختلفی را آزمایش کنید. در اسکرین‌شات بالا، هر سه پارامتر موجود هستند و همگی نامعتبرند؛
  • در [12-15]، پاسخ سرور jSON؛

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

Image

بالا،

  • در [5-8]، تنها پارامتر [salaire] ارسال شده است و علاوه بر این، نامعتبر است؛
  • در [9-11]، نتیجه jSON از سرور؛

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

Image

بالا:

  • در [1118]، درخواستی با پارامترهای معتبر [6-8];
  • در [12-14]، پاسخ سرور jSON;

23.11.7. اقدام [lister-simulations]

اقدام [lister-simulations] توسط کنترل‌کننده ثانویه زیر [ListerSimulationsController] پردازش می‌شود:


<?php

namespace Application;

//وابستگی‌های Symfony
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;

class ListerSimulationsController {

  // $config پیکربندی برنامه است
  //پردازش یک درخواست
  //از Session استفاده می‌کند و می‌تواند آن را تغییر دهد
  //QZXW2HTMLCJG جزئیات اضافی مختص هر کنترلر هستند
  //یک آرایه را بازمی‌گرداند [$statusCode, $état, $content, $headers]
  public function execute(
    array $config,
    Request $request,
    Session $session,
    array $infos = NULL): array {

    //باید یک پارامتر واحد GET باشد
    $method = strtolower($request->getMethod());
    $erreur = $method !== "get" || $request->query->count() != 1;
    if ($erreur) {
      $état = 501;
      $message = "GET requis, avec l'unique paramètre [action] dans l'URL";
      //یک نتیجه با خطا را به کنترل‌کننده اصلی بازمی‌گرداند
      return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
    }
    // فهرست شبیه‌سازی‌ها در جلسه را بازیابی می‌کند
    if (!$session->has("simulations")) {
      $simulations = [];
    } else {
      $simulations = $session->get("simulations");
    }
    // نتیجهٔ موفقیت‌آمیز را به کنترلر اصلی بازمی‌گرداند
    $état = 500;
    return [Response::HTTP_OK, $état, ["réponse" => $simulations], []];
  }

}

نظرات

  • درخواست [GET main.php?action=lister-simulations];
  • خطوط ۲۴–۲۵: بررسی می‌شود تا اطمینان حاصل شود که یک درخواست GET با یک پارامتر وجود دارد؛
  • خطوط ۲۶–۳۱: اگر اینطور نباشد، یک نتیجه خطا به کنترل‌کننده اصلی بازگردانده می‌شود؛
  • خطوط ۳۳–۳۷: اگر لیست شبیه‌سازی‌ها در جلسه موجود باشد، آن را بازیابی می‌کنیم (خط ۳۶)؛ در غیر این صورت، این لیست خالی است (خط ۳۴)؛
  • خطوط ۳۹–۴۰: فهرست شبیه‌سازی‌ها به کنترلر اصلی بازگردانده می‌شود؛

23.11.8. تست‌های [Postman]

ما قصد داریم دو تست ایجاد کنیم: یکی برای خطا و دیگری برای نتیجهٔ موفقیت‌آمیز.

Image

بالا:

  • در [1-8]، ما یک درخواست [GET] با یک پارامتر اضافی [param1] در URL [3, 7-8] انجام می‌دهیم؛
  • [9-12]، پاسخ سرور jSON؛

حالا یک درخواست معتبر بسازیم:

Image

بالا:

  • [1-5]، یک درخواست معتبر؛

نتیجه درخواست به شرح زیر است:

Image

  • در [3-6]، پاسخ سرور jSON است. پیش از این آزمایش، آزمایش [Postman] [calculer-impot-300] چندین بار برای ایجاد شبیه‌سازی‌ها در جلسه وب سرور اجرا شده بود؛

23.11.9. اقدام [supprimer-simulation]

اقدام [supprimer-simulation] توسط کنترل‌کننده ثانویه زیر [SupprimerSessionController] مدیریت می‌شود:


<?php

namespace Application;

//وابستگی‌های Symfony
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;

class SupprimerSimulationController {

  /// $config پیکربندی برنامه است
  // پردازش یک درخواست
  //از جلسه استفاده می‌کند و می‌تواند آن را تغییر دهد
  //$infos جزئیات اضافی مختص هر کنترل‌کننده هستند
  //یک آرایه را بازمی‌گرداند [$statusCode, $état, $content, $headers]
  public function execute(
    array $config,
    Request $request,
    Session $session,
    array $infos = NULL): array {

    //باید دو پارامتر وجود داشته باشد: GET
    $method = strtolower($request->getMethod());
    $erreur = $method !== "get" || $request->query->count() != 2;
    $état = 600;
    if ($erreur) {
      $état += 2;
      $message = "GET requis, avec les paramètres [action, numéro]";
    }
    //پارامتر [numéro] باید وجود داشته باشد
    if (!$erreur) {
      $état += 4;
      $erreur = !$request->query->has("numéro");
      if ($erreur) {
        $message = "paramètre [numéro] manquant";
      }
    }
    //پارامتر [numéro] باید معتبر باشد
    if (!$erreur) {
      $état += 8;
      $numéro = $request->query->get("numéro");
      $erreur = !preg_match("/^\d+$/", $numéro);
      if ($erreur) {
        $message = "paramètre [$numéro] invalide";
      }
    }
    //پارامتر [numéro] باید در بازه [0,n-1] باشد
    // اگر n تعداد شبیه‌سازی‌ها باشد
    if (!$erreur) {
      $numéro = (int) $numéro;
      $erreur = !$session->has("simulations");
      if (!$erreur) {
        $simulations = $session->get("simulations");
        $erreur = $numéro < 0 || $numéro >= count($simulations);
      }
      if ($erreur) {
        $état += 16;
        $message = "la simulation n° [$numéro] n'existe pas";
      }
    }
    //خطا؟
    if ($erreur) {
      // نتیجه به کنترل‌کننده اصلی بازگردانده می‌شود
      return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
    }
    //شبیه‌سازی حذف می‌شود $numéro
    unset($simulations[$numéro]);
    $simulations = array_values($simulations);
    //شبیه‌سازی‌ها به جلسه بازگردانده می‌شوند
    $session->set("simulations", $simulations);
    // بازگرداندن لیست شبیه‌سازی‌ها به کلاینت
    $état = 600;
    return [Response::HTTP_OK, $état, ["réponse" => $simulations], []];
  }

}

نظرات

  • درخواست [GET main.php?action=supprimer-simulation&numéro=x];
  • خطوط ۲۴–۳۰: بررسی می‌کنیم که یک درخواست GET با دو پارامتر وجود دارد؛
  • خطوط ۳۲–۳۸: بررسی می‌کنیم که پارامتر [numéro] در میان پارامترهای URL وجود دارد؛
  • خطوط ۴۰–۴۷: بررسی می‌کنیم که مقدار پارامتر [numéro] از نظر نحوی صحیح است؛
  • خطوط 50–61: بررسی می‌کنیم که شبیه‌سازی شماره [numéro] واقعاً وجود دارد. دو خطای احتمالی وجود دارد:
    • فهرست شبیه‌سازی‌ها در جلسه یافت نمی‌شود (خط ۵۲)؛
    • شناسه شبیه‌سازی [numéro] که باید حذف شود، در فهرست شبیه‌سازی‌ها وجود ندارد؛
  • خطوط ۶۳–۶۶: در صورت بروز خطا، یک پیام خطا به کنترل‌کننده اصلی بازگردانده می‌شود؛
  • خط ۶۸: شبیه‌سازی شماره [numéro] حذف می‌شود؛
  • خط ۶۹: عملیات [unset] شاخص‌های [0, n-1] را در لیست تغییر نمی‌دهد. برای به‌روزرسانی آن‌ها، مقادیر آرایه [$simulations] بازیابی می‌شوند تا شبیه‌سازی گم‌شده حذف شود؛
  • خط ۷۱: آرایه جدید شبیه‌سازی‌ها مجدداً در جلسه درج می‌شود؛
  • خطوط ۷۳–۷۴: فهرست جدید شبیه‌سازی‌ها به کنترل‌کننده اصلی بازگردانده می‌شود؛

23.11.10. آزمایش‌های [Postman]

ما هم تست‌های خطا و هم تست‌های موفقیت را انجام خواهیم داد:

Image

بالا:

  • در [1-6]، یک درخواست GET بدون پارامتر [numéro]؛
  • در [7-10]، پاسخ سرور jSON;

اکنون درخواستی با عددی از نظر نحوی نادرست:

Image

بالا:

  • در [1-5]، یک درخواست GET با یک پارامتر نامعتبر [numéro] [3, 5];
  • در [6-9]، پاسخ سرور jSON;

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

Image

بالا:

  • در [1-5]، درخواستی با شماره شبیه‌سازی ۱۰۰، که در لیست شبیه‌سازی‌ها وجود ندارد؛
  • در [6-9]، پاسخ سرور jSON؛

اکنون، ما شبیه‌سازی شماره ۰ را از لیست حذف می‌کنیم، یعنی اولین شبیه‌سازی. ابتدا، بیایید دوباره این لیست را با استفاده از پرس‌وجوی [lister-simulations-500] درخواست کنیم:

Image

  • در [1] در حال حاضر ۲ شبیه‌سازی وجود دارد؛

ما اولین شبیه‌سازی (شماره ۰) را حذف می‌کنیم:

Image

بالا:

  • در [1-5]، شبیه‌سازی شماره ۰ ([5]) حذف شده است؛
  • در [6-9]، پاسخ سرور jSON است. می‌بینیم که شبیه‌سازی شماره ۰ حذف شده است؛

بیایید این عملیات را تکرار کنیم:

Image

بالا:

  • در [1]، دیگر هیچ شبیه‌سازی‌ای در جلسه وب سرور باقی نمانده است؛

23.11.11. عمل [fin-session]

اقدام [fin-session] توسط کنترل‌کننده ثانویه زیر، [FinSessionController]، پردازش می‌شود:


<?php

namespace Application;

//وابستگی‌های Symfony
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;

class FinSessionController implements InterfaceController {

  // $config پیکربندی برنامه است
  //پردازش یک درخواست
  //از Session استفاده می‌کند و می‌تواند آن را تغییر دهد
  // QZXW2HTMLCJG جزئیات اضافی مختص هر کنترلر هستند
  //یک آرایه را بازمی‌گرداند [$statusCode, $état, $content, $headers]

  public function execute(
    array $config,
    Request $request,
    Session $session,
    array $infos = NULL): array {

    //باید یک پارامتر واحد GET وجود داشته باشد
    $method = strtolower($request->getMethod());
    $erreur = $method !== "get" || $request->query->count() != 1;
    //خطا؟
    if ($erreur) {
      $état = 401;
      // نتیجه به کنترلر اصلی ارسال می‌شود
      $message = "GET requis avec le seul paramètre [action] dans l'URL";
      return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
    }

    // نوع جلسه ذخیره می‌شود
    $type = $session->get("type");
    // اعتبار جلسهٔ جاری را لغو می‌کند
    $session->invalidate();
    // تنظیم نوع برای جلسه جدید
    $session->set("type", $type);
    //ارسال پاسخ
    $état = 400;
    // نتیجه برای کنترلر اصلی ارسال می‌شود
    $content = ["réponse" => "session supprimée"];
    return [Response::HTTP_OK, $état, $content, []];
  }

}

نظرات

  • درخواست [GET main.php?action=fin-session];
  • خطوط ۲۵–۳۳: بررسی می‌شود که آیا اقدام، یک GET با پارامتر واحد [fin-action] است؛
  • خط ۳۸: جلسهٔ جاری باطل می‌شود. این کار داده‌های ذخیره‌شده در آن را حذف می‌کند و یک جلسهٔ جدید آغاز می‌شود؛
  • خط ۳۶: قبل از پایان جلسه، نوع آن، [json, xml, htmlذخیره می‌شود؛
  • خط ۴۰: نوع جلسه قبلی در جلسه جدید بازیابی می‌شود. در نهایت، یک جلسه جدید با کلید منحصربه‌فرد [type] آغاز می‌شود؛
  • خطوط ۴۴–۴۵: نتیجه به کنترل‌کننده اصلی بازگردانده می‌شود؛

23.11.12. آزمایش‌های [Postman]

ما یک تست خطا و یک تست موفقیت را انجام خواهیم داد:

Image

بالا:

  • در [1-5]، ما پایان جلسه را در [5] با یک POST [2] به جای GET مورد انتظار درخواست می‌کنیم؛
  • در [6-9]، پاسخ سرور jSON؛

اکنون یک مثال موفق. ابتدا بیایید به کوکی جلسه مبادله شده بین کلاینت [Postman] و سرور در طول آخرین آزمایش انجام شده نگاهی بیندازیم:

Image

بالا:

  • [3]، کوکی جلسه ارسال‌شده توسط کلاینت [Postman] به سرور؛

اکنون بیایید به هدرهای ارسال‌شده توسط سرور در پاسخ آن، HTTP، نگاهی بیندازیم:

Image

بالا:

  • در [3-4]، کوکی جلسه در پاسخ سرور گنجانده نشده است. این طبیعی است. سرور فقط یک بار آن را ارسال می‌کند: در ابتدای یک جلسه وب جدید؛

اکنون بیایید یک اقدام معتبر [fin-session] را اجرا کنیم:

Image

بالا:

  • در [1-3]، یک اقدام معتبر [fin-session]؛
  • در [4-7]، پاسخ سرور jSON؛

بیایید سربرگ‌های HTTP ارسال‌شده در پاسخ سرور را بررسی کنیم:

Image

  • در [3]، سرور هدر [Set-Cookie] را ارسال می‌کند و بدین ترتیب نشان می‌دهد که یک جلسه وب جدید در حال شروع است؛

23.12. انواع پاسخ‌های سرور

23.12.1. مقدمه

بیایید نگاهی دیگر به معماری کلی برنامه بیندازیم:

Image

اکنون انواع پاسخ‌های ممکن را که در [3a] قرار دارند، تشریح خواهیم کرد. این موارد در پوشه [Responses] درون پروژه موجود هستند:

Image

ما قبلاً کلاس [JsonResponse] را در بخش «لینک» معرفی کرده‌ایم. این کلاس رابط [InterfaceResponse] را پیاده‌سازی می‌کند و از کلاس [ParentResponse] ارث می‌برد. همین امر در مورد دو کلاس دیگر، [XmlResponse] و [HtmlResponse] نیز صدق می‌کند.

بیایید تعریف رابط [InterfaceResponse] را به یاد بیاوریم:


<?php

namespace Application;

// وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

interface InterfaceResponse {

  // درخواست $request: در حال پردازش
  // Session $session: جلسهٔ برنامهٔ وب
  // آرایه $config: پیکربندی برنامه
  // int statusCode: کد وضعیت پاسخ HTTP
  // آرایه $content: پاسخ سرور
  // آرایه $headers: سربرگ‌های HTTP که باید به پاسخ اضافه شوند
  // Logger $logger: لاگر برای نوشتن لاگ‌ها
  
  public function send(
    Request $request = NULL,
    Session $session = NULL,
    array $config,
    int $statusCode,
    array $content,
    array $headers,
    Logger $logger = NULL): void;
}
  • خطوط ۱۹–۲۷: رابط [InterfaceResponse] دارای یک متد واحد، [sendبرای ارسال پاسخ به کلاینت است؛
  • خطوط ۱۱–۱۷: معنای پارامترهای مختلف متد [send]؛
  • خطوط ۲۳–۲۵: پارامترهای [$statusCode, $content, $headers] پاسخ استاندارد از کنترل‌کننده‌های ثانویه برنامه را تشکیل می‌دهند. با این حال، پاسخ ممکن است به اطلاعات اضافی نیاز داشته باشد. بنابراین، این اطلاعات با سه پارامتر اول (خطوط ۲۰–۲۲) که دسترسی به تمام اطلاعات مربوط به درخواست، جلسه و پیکربندی را فراهم می‌کنند، ارائه می‌شود؛
  • خط ۲۶: پاسخ به [Logger] نیاز دارد زیرا پاسخ ارسال‌شده به کلاینت را ثبت (لاگ) خواهد کرد؛

اکنون بیایید کد کلاس [ParentResponse]، کلاس والد سه نوع پاسخ، را که مفهومی مشترک میان آن‌ها را انتزاع می‌کند، به یاد آوریم: ارسال واقعی یک پاسخ متنی به کلاینت:


<?php

namespace Application;

//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Response;

class ParentResponse {

  // int $statusCode: کد وضعیت پاسخ HTTP
  // رشته $content: بدنه پاسخ ارسالی
  //بسته به مورد، این یک رشته است: jSON, XML, HTML
  //آرایه $headers: سربرگ‌های HTTP که باید به پاسخ اضافه شوند

  public function sendResponse(
    int $statusCode,
    string $content,
    array $headers): void {

    //آماده‌سازی پاسخ متنی سرور
    $response = new Response();
    $response->setCharset("utf-8");
    //کد وضعیت
    $response->setStatusCode($statusCode);
    // سربرگ‌ها
    foreach ($headers as $text => $value) {
      $response->headers->set($text, $value);
    }
    // پاسخ ارسال می‌شود
    $response->setContent($content);
    $response->send();
  }
}

توضیحات

  • خطوط ۱۰–۱۳: معنای سه پارامتر متد [send]؛
  • خط ۱۷: توجه کنید که بدنه پاسخ از نوع [string] است و بنابراین آماده ارسال است (خط ۳۰)؛
  • خط ۲۲: پاسخ حاوی کاراکترهایی از نوع UTF-8 خواهد بود؛
  • خط ۲۴: کد وضعیت پاسخ HTTP;
  • خطوط ۲۶–۲۸: افزودن سربرگ‌های HTTP ارائه‌شده توسط کد فراخوانی؛
  • خطوط ۳۰–۳۱: ارسال پاسخ به کلاینت؛

در نهایت، بیایید کد کنترل‌کننده اصلی را که درخواست ارسال پاسخ به مشتری را می‌کند، به یاد آوریم:


//افزودن کلیدهای [action, état] به پاسخ کنترل‌کننده
$content = ["action" => $action, "état" => $état] + $content;
//شیء [Response] که مسئول ارسال پاسخ به کلاینت است، ایجاد می‌شود
$response = __NAMESPACE__ . $config["types"][$type]["response"];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);

//پاسخ ارسال شده است – منابع آزاد می‌شوند
$logger->close();
exit;
  • خط ۴: نام کلاسی که باید نمونه سازی شود روی [Response] تنظیم می‌شود؛
  • خط ۵: کلاس نمونه برداری شده و پاسخ با استفاده از متد [send($request, $session, $config, $statusCode, $content, $headers, $logger)] برای کلاینت ارسال می‌شود. از آنجایی که آنها همان اینترفیس [InterfaceResponse] را پیاده‌سازی می‌کنند، متدهای [send] برای انواع مختلف پاسخ همگی امضای یکسانی دارند؛

23.12.2. کلاس [JsonResponse]

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

کلاس [JsonResponse] رابط [InterfaceResponse] را به شرح زیر پیاده‌سازی می‌کند:


<?php

namespace Application;

//وابستگی‌های Symfony
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;

class JsonResponse extends ParentResponse implements InterfaceResponse {

  //درخواست $request: درخواست در حال پردازش است
  // جلسه $session: جلسه برنامه وب
  // آرایه $config: پیکربندی برنامه
  // int statusCode: کد وضعیت پاسخ HTTP
  // آرایه $content: پاسخ سرور
  //آرایه $headers: سربرگ‌های HTTP که باید به پاسخ اضافه شوند
  // لاگر $logger: لاگری برای نوشتن لاگ‌ها

  public function send(
    Request $request = NULL,
    Session $session = NULL,
    array $config,
    int $statusCode,
    array $content,
    array $headers,
    Logger $logger = NULL): void {

    //آماده‌سازی سریالایزر Symfony
    $serializer = new Serializer(
      [
      // برای سریال‌سازی اشیاء مورد نیاز است
      new ObjectNormalizer()],
      // jSON رمزگذار
      //برای گزینه‌ها، OU را بین گزینه‌های مختلف قرار دهید
      [new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))]
    );
    //سریالی‌سازی jSON
    $json = $serializer->serialize($content, 'json');
    // سربرگ‌ها
    $headers = array_merge($headers, ["content-type" => "application/json"]);
    //ارسال پاسخ
    parent::sendResponse($statusCode, $json, $headers);
    // لاگ
    if ($logger !== NULL) {
      $logger->write("réponse=$json\n");
    }
  }

}

توضیحات

  • خط ۱۳: این کلاس رابط [InterfaceResponse] را پیاده‌سازی می‌کند؛
  • خط ۱۳: این کلاس از کلاس [ParentResponse] ارث می‌برد. تمام انواع [Response] از این کلاس ارث می‌برند. این کلاس والد است که پاسخ را به کلاینت ارسال می‌کند (خط ۴۶). از آنجا که این کد در همه انواع [Response] مشترک بود، به یک کلاس والد استخراج شد؛
  • خطوط ۳۳–۴۰: نمونه‌سازی سریالایزر [Symfony]، که پاسخ را از سرور [$content] به یک رشته jSON تبدیل می‌کند (خط ۴۲)؛
  • خطوط ۳۴–۳۶: اولین پارامتر سازنده [Serializer] یک آرایه است. این آرایه شامل یک نمونه از کلاس [ObjectNormalizer] است که برای سریالی‌سازی اشیاء مورد نیاز است. در این برنامه، این کار با یک لیست از شبیه‌سازی‌ها انجام می‌شود که در آن هر شبیه‌سازی یک نمونه از کلاس [Simulation] است؛
  • خط ۳۹: پارامتر دوم سازنده [Serializer] نیز یک آرایه است: این شامل تمام انکودرهای مورد استفاده در یک سریالیزاسیون است (XML، jSON، CSV و غیره)؛
  • خط ۳۹: در اینجا تنها یک رمزگذار از نوع [JsonEncoder] وجود خواهد داشت. ممکن بود سازنده بدون پارامتر کافی باشد. در اینجا، ما پارامتر [JsonEncode] را صرفاً برای ارسال گزینه‌های رمزگذاری jSON به سازنده پاس کرده‌ایم؛
  • خط ۳۹: پارامتر سازنده [JsonEncode] یک آرایه از گزینه‌ها است. در اینجا، گزینه [JSON_UNESCAPED_UNICODE] برای درخواست نمایش کاراکترهای UTF-8 در رشته jSON به‌صورت بومی (natively) به‌جای «escaped» استفاده می‌شود؛
  • خط ۴۲: بدنه پاسخ HHTP با استفاده از سریالایزر قبلی به jSON سریال می‌شود؛
  • خط ۴۴: هدر HTTP اضافه می‌شود که به کلاینت اطلاع می‌دهد jSON برای او ارسال خواهد شد؛
  • خط ۴۶: به کلاس والد دستور داده می‌شود که پاسخ را برای کلاینت ارسال کند؛
  • خطوط ۴۸–۵۰: ما پاسخ jSON را ثبت می‌کنیم؛

23.12.3. کلاس [XmlResponse]

کلاس [XmlResponse] رابط [InterfaceResponse] را به شرح زیر پیاده‌سازی می‌کند:


<?php

namespace Application;

//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

class XmlResponse extends ParentResponse implements InterfaceResponse {

  // درخواست $request: درخواست در حال پردازش
  // Session $session: جلسهٔ برنامهٔ وب
  // آرایه $config: پیکربندی برنامه
  // int statusCode: کد وضعیت پاسخ HTTP
  // آرایه $content: پاسخ سرور
  // آرایه $headers: سربرگ‌های HTTP که باید به پاسخ اضافه شوند
  // لاگ‌گیر $logger: لاگ‌گیر برای نوشتن لاگ‌ها

  public function send(
    Request $request = NULL,
    Session $session = NULL,
    array $config,
    int $statusCode,
    array $content,
    array $headers,
    Logger $logger = NULL): void {

    //آماده‌سازی سریالایزر Symfony
    $serializer = new Serializer(
      // مورد نیاز برای سریالی‌سازی اشیاء
      [new ObjectNormalizer()],
      [
      // سریال‌سازی XML
      new XmlEncoder(
        [
        XmlEncoder::ROOT_NODE_NAME => 'root',
        XmlEncoder::ENCODING => 'utf-8'
        ]
      ),
      // سریال‌سازی jSON
      new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))
      ]
    );
    //سریالی‌سازی XML
    $xml = $serializer->serialize($content, 'xml');
    // سربرگ‌ها
    $headers = array_merge($headers, ["content-type" => "application/xml"]);
    // پاسخ ارسال شد
    parent::sendResponse($statusCode, $xml, $headers);
    // لاگ
    if ($logger !== NULL) {
      // ورود به jSON
      $log = $serializer->serialize($content, 'json');
      $logger->write("réponse=$log\n");
    }
  }

}

نظرات

  • خطوط ۳۴–۴۸: نمونه‌سازی یک سریالایزر Symfony. سازنده دو پارامتر آرایه‌ای را می‌پذیرد؛
  • خط ۳۶: آرایه اول شامل یک نمونه از نوع [ObjectNormalizer] است که در سریالیزاسیون شیء استفاده می‌شود؛
  • خطوط ۳۷–۴۷: آرایه دوم شامل رمزگذارهای مورد استفاده برای سریالی‌سازی است. انواع مختلفی از سریالی‌سازی را می‌توان با استفاده از همان سریالی‌ساز مشخص کرد؛
  • خطوط ۳۸–۴۴: رمزگذار XML؛
  • خط ۴۱: ریشه کد تولید شده XML تنظیم می‌شود. این به شکل <root>[autres balises XML]</root> درخواهد آمد؛
  • خط ۴۲: رمزگذاری از کاراکترهای UTF-8 استفاده خواهد کرد؛
  • خط ۴۶: رمزگذار jSON. این برای ثبت پاسخ در فایل [logs.txt] که در jSON نوشته می‌شود، استفاده خواهد شد؛
  • خط ۵۰: بدنه پاسخ ارسال‌شده به کلاینت در XML سریال می‌شود؛
  • خط ۵۲: هدر HTTP به هدرهای دریافت‌شده به‌عنوان پارامترها (خط ۳۰) اضافه می‌شود؛ این هدر به کلاینت اطلاع می‌دهد که یک سند XML برای او ارسال می‌شود؛
  • خط ۵۴: کلاس والد در واقع پاسخ را برای کلاینت ارسال می‌کند؛
  • خطوط ۵۶–۶۰: ثبت پاسخ در jSON;

23.12.4. آزمایش‌های [Postman]

ما قبلاً تمام تست‌های خطای ممکن را در jSON انجام داده‌ایم. در XML دیگر کاری باقی نمانده است. ما دو مثال از پاسخ XML را نشان می‌دهیم:

Image

بالا:

  • در [1-3]، درخواست شروع جلسه XML;
  • در [4-7]، پاسخ سرور XML؛

از این پس، تمام پاسخ‌های سرور در XML خواهند بود. ما می‌توانیم همه درخواست‌هایی را که قبلاً در [Postman] استفاده شده‌اند بدون تغییر دوباره استفاده کنیم و برای هر یک از آن‌ها پاسخی در XML دریافت خواهیم کرد. بیایید یک احراز هویت موفق را به‌عنوان مثال در نظر بگیریم:

Image

بالا:

  • در [1-3]، یک درخواست احراز هویت معتبر؛
  • [4-7]، پاسخ سرور XML؛

23.12.5. پاسخ [HtmlResponse]

وقتی نوع جلسه [html] باشد، یک شی از نوع [HtmlResponse] برای ارسال پاسخ به کلاینت ایجاد می‌شود. این یک جریان HTML را برای کلاینت ارسال می‌کند که به کد وضعیت بازگردانده‌شده توسط کنترل‌کننده ثانویه‌ای که اقدام را پردازش کرده است، بستگی دارد. این نگاشت، [état=>vue]، به صورت زیر در فایل پیکربندی [config.json] وارد می‌شود:


"vues": {
        "vue-authentification.php": [700, 221, 400],
        "vue-calcul-impot.php": [200, 300, 341, 350, 800],
        "vue-liste-simulations.php": [500, 600]
    },
"vue-erreurs": "vue-erreurs.php"

این پیکربندی به شرح زیر تفسیر می‌شود: [‘nom de la vue’ => ‘états associés à cette vue’]

  • خط ۲: اگر کنترل‌کننده ثانویه وضعیتی را از آرایه [700, 221, 400] برگردانده باشد، آنگاه نما [vue-authentification.php] باید نمایش داده شود؛
  • خط ۳: اگر کنترل‌کننده ثانویه وضعیتی را از جدول [200, 300, 341, 350, 800] برگردانده باشد، آنگاه نما [vue-calcul-impot.php] باید نمایش داده شود؛
  • خط ۴: اگر کنترل‌کننده ثانویه وضعیتی را از جدول [500, 600] بازگردانده باشد، آنگاه نما [vue-liste-simulations.php] باید نمایش داده شود؛
  • خط ۶: اگر کنترل‌کننده ثانویه وضعیتی را بازگردانده باشد که در هیچ‌یک از جدول‌های قبلی نباشد، آنگاه نما [vue-erreurs.php] باید نمایش داده شود؛

ویوها در پوشه [Views] پروژه گروه‌بندی شده‌اند:

Image

کد کلاس [HtmlResponse] به شرح زیر است:


<?php

namespace Application;

//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

class HtmlResponse extends ParentResponse implements InterfaceResponse {

  // درخواست $request: در حال پردازش
  // جلسه $session: جلسه برنامه وب
  // آرایه $config: پیکربندی برنامه
  // int statusCode: کد وضعیت پاسخ HTTP
  // آرایه $content: پاسخ سرور
  // آرایه $headers: سربرگ‌های HTTP که باید به پاسخ اضافه شوند
  // لاگ‌گیر $logger: لاگ‌گیر برای نوشتن لاگ‌ها

  public function send(
    Request $request = NULL,
    Session $session = NULL,
    array $config,
    int $statusCode,
    array $content,
    array $headers,
    Logger $logger = NULL): void {

    //آماده‌سازی سریالایزر Symfony
    $serializer = new Serializer(
      [
      //برای سریال‌سازی اشیاء
      new ObjectNormalizer()],
      [
      //برای سریالی‌سازی jSON لاگ پاسخ
      new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))
      ]
    );
    //پاسخ HTML به کد وضعیت بازگردانده‌شده توسط کنترلر بستگی دارد
    $état = $content["état"];
    // هر وضعیت با یک نما مطابقت دارد – این در پیکربندی برنامه جستجو می‌شود
    // فهرست ویوها
    $vues = array_keys($config["vues"]);
    $trouvé = false;
    $i = 0;
    // فهرست ویوها به‌صورت متوالی بررسی می‌شود
    while (!$trouvé && $i < count($vues)) {
      //گزارش‌های مرتبط با نمای شماره i
      $états = $config["vues"][$vues[$i]];
      //آیا گزارش در میان گزارش‌های مرتبط با نما شماره I جستجو می‌شود؟
      if (in_array($état, $états)) {
        //نمای نمایش‌داده‌شده نمای شمارهٔ i خواهد بود
        $vueRéponse = $vues[$i];
        $trouvé = true;
      }
      // نما بعدی
      $i++;
    }
    // یافت شد؟
    if (!$trouvé) {
      //اگر برای وضعیت فعلی برنامه هیچ نمایی وجود نداشته باشد
      //نمای خطا نمایش داده می‌شود
      $vueRéponse = $config["vue-erreurs"];
    }
    // بازیابی نمای HTML برای نمایش به‌عنوان یک رشته
    ob_start();
    require __DIR__ . "/../Views/$vueRéponse";
    $html = ob_get_clean();
    //سربرگ‌ها تنظیم می‌شوند تا نشان دهند که HTML باید ارسال شود
    $headers = array_merge($headers, ["content-type" => "text/html"]);
    //کلاس والد ارسال واقعی پاسخ را مدیریت می‌کند
    parent::sendResponse($statusCode, $html, $headers);
    //ثبت در jSON قالب پاسخ بدون HTML
    if ($logger !== NULL) {
      // لاگ در jSON از پاسخ کنترل‌کننده ثانویه‌ای که اقدام را پردازش کرده است
      $log = $serializer->serialize($content, 'json');
      $logger->write("réponse=$log\n");
    }
  }

}

نظرات

  • خطوط ۳۲–۴۱: یک سریالایزر Symfony نمونه سازی می‌شود. این برای لاگ jSON پاسخ از کنترولری که اقدام را پردازش کرده است (خطوط ۷۲–۸۲) مورد نیاز است؛
  • خطوط ۴۲–۵۷: پیکربندی برنامه برای یافتن ویوی نمایش داده شده جستجو می‌شود. این امر به کد وضعیت بازگردانده شده توسط کنترلری که اقدام را پردازش کرده است بستگی دارد. این کد در [$content[‘état’]] (خط ۴۳) قرار دارد؛
  • خطوط ۴۲–۶۱: نمای متناظر با این وضعیت جستجو می‌شود؛
  • خطوط ۶۲–۶۷: اگر هیچ ویویی پیدا نشده باشد، آنگاه برنامه در وضعیت غیرطبیعی با کد وضعیت HTML قرار دارد. ما بعداً این مفهوم وضعیت‌های غیرطبیعی را با جزئیات بیشتری توضیح خواهیم داد. در این حالت، یک ویوی خطا نمایش داده می‌شود؛
  • خطوط 68–70: کد PHP نمای انتخاب‌شده تفسیر شده و نتیجه در متغیر [$html] (خط 71) ذخیره می‌شود؛
  • این کد نیاز به توضیح دارد. بیایید فرض کنیم که نمای انتخاب‌شده [vue-authentification.php] است که یک فرم احراز هویت وب را نمایش می‌دهد:
    • خط ۶۹: تابع [ob_start] چیزی را آغاز می‌کند که در مستندات به آن «تأخیر خروجی» (output delay) گفته می‌شود. هر چیزی که توسط عملیات print یا require نوشته شود—که معمولاً بلافاصله برای کلاینت ارسال می‌شود—در یک بافر خروجی (ob) قرار می‌گیرد بدون اینکه برای کلاینت ارسال شود؛
    • خط ۷۰: نما [vue-authentification.php] بارگذاری می‌شود؛ این یک نمای پویا HTML است که حاوی کد PHP می‌باشد. سپس دو اتفاق می‌افتد:
      • کد PHP از نمای [vue-authentification.php] بارگذاری و تفسیر می‌شود. نتیجه یک نما است که آن را [vue-authentification.html] می‌نامیم، که فقط حاوی کد HTML، یا حتی CSS و جاوا اسکریپت است، اما دیگر هیچ PHP در آن وجود ندارد؛
      • این کد HTML معمولاً به کلاینت ارسال می‌شود. در واقع این امر برای هر متنی که تفسیرگر PHP با آن مواجه می‌شود و کد PHP نیست، صادق است. به دلیل تأخیر در خروجی، این کد HTML در بافر خروجی قرار می‌گیرد بدون اینکه به کلاینت ارسال شود؛
    • خط ۷۱: تابع [ob_get_clean] دو کار انجام می‌دهد:
      • محتویات بافر خروجی – یعنی صفحه‌ی [vue-authentification.html] که در آنجا قرار گرفته بود – را در متغیر [$html] ذخیره می‌کند؛
      • حافظهٔ خروجی را پاک می‌کند. از نظر حافظهٔ خروجی، گویی هیچ اتفاقی نیفتاده است. علاوه بر این، کلاینت هنوز هیچ چیزی دریافت نکرده است؛
  • خط ۷۰: ما در حال حاضر کلاس [HtmlResponse] را که در پوشه [Responses] قرار دارد، اجرا می‌کنیم. برای یافتن ویو، بنابراین باید یک سطح بالاتر برویم به [..] و سپس وارد پوشه [Views] شویم. [__DIR__] نام مطلق پوشه‌ای است که اسکریپت در حال اجرا را در خود دارد؛ در مثال ما، این پوشه [C:/myprograms/laragon-lite/www/php7/scripts-web/impots/13/Responses] است؛
  • خط ۷۳: ما هدر را که به کلاینت می‌گوید قصد داریم HTML را برای او ارسال کنیم، به هدرهای HTTP که به‌عنوان پارامتر (خط ۲۹) دریافت شده‌اند، اضافه می‌کنیم؛
  • خط ۷۵: به کلاس والد دستور داده می‌شود که در واقع پاسخ را برای کلاینت ارسال کند؛
  • خطوط ۷۷–۸۱: پاسخ [$content] که توسط کنترل‌کننده ثانویه‌ای که اقدام جاری را پردازش کرده ارائه شده است، با شناسه jSON ثبت می‌شود؛

23.12.6. آزمایش‌های [Postman]

برای تست صحیح حالت HTML جلسه، باید همهٔ ویوها را بررسی کنیم. این کار را در مرحلهٔ بعد انجام خواهیم داد. تست زیر را اجرا خواهیم کرد:

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


"vues": {
        "vue-authentification.php": [700, 221, 400],
        "vue-calcul-impot.php": [200, 300, 341, 350, 800],
        "vue-liste-simulations.php": [500, 600]
    },
    "vue-erreurs": "vue-erreurs.php"

ما می‌توانیم با بررسی تست‌های [Postman] که انجام شده‌اند، زمینه تولید برخی از کدهای وضعیت فوق را شناسایی کنیم:

Image

می‌توانیم ببینیم که کد وضعیت [700] با یک اقدام موفق [init-session] ([2]) مطابقت دارد. در بالا، ما یک پاسخ jSON داریم، اما این پاسخ می‌تواند از نوع XML یا HTML نیز باشد. این مورد آخر است که مورد آزمایش قرار خواهد گرفت. طبق فایل پیکربندی، نما [vue-authentification.php] پاسخ HTML را تشکیل می‌دهد. بیایید بررسی کنیم.

Image

بالا:

  • در [1-3]، یک جلسه HTML آغاز می‌شود. بنابراین ما یک پاسخ HTML را انتظار داریم؛
  • در [4-8]، پاسخ سرور HTML است؛
  • زبانه‌ی [8] به شما امکان می‌دهد پیش‌نمایشی از کد دریافتی HTML را مشاهده کنید؛

Image

  • در [8-9]، پیش‌نمایش نمای HTML؛

23.13. برنامه وب HTML

23.13.1. مروری بر نماها

برنامه وب HTML از چهار نما استفاده خواهد کرد:

نمایه احراز هویت:

Image

نما محاسبه مالیات:

Image

نمایه فهرست شبیه‌سازی:

Image

نمايش خطاهای غيرمنتظره:

Image

ما این نماها را یکی یکی توضیح خواهیم داد.

23.13.2. نماى احراز هویت

23.13.2.1. نمای کلی

نمای احراز هویت به شرح زیر است:

Image

این نما از دو عنصر تشکیل شده است که ما آن‌ها را قطعات می‌نامیم:

  • قطعه [1] توسط اسکریپت [v-bandeau.php] تولید می‌شود؛
  • قطعه [2] توسط اسکریپت [v-authentification.php] تولید می‌شود؛

نمایه احراز هویت توسط صفحه زیر، [vue-authentification.php تولید می‌شود:


<?php
//داده‌های آزمایشی برای صفحه
//داده‌های صفحه در $page جاسازی شده است

?>

<!doctype html>
<html lang="fr">
    <head>
        <!-- برچسب‌های متای مورد نیاز -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!--بوت‌استرپ CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
        <title>Application impots</title>
    </head>
    <body>
        <div class="container">
            <!-- سربرگ با ۱ ردیف و ۱۲ ستون -->
            <?php require "v-bandeau.php"; ?>
            <!-- فرم ورود ۹ ستونی -->
            <div class="row">
                <div class="col-md-9">
                    <?php require "v-authentification.php" ?>
                </div>
            </div>  
            <?php
            // اگر خطایی رخ دهد – هشدار خطا را نمایش دهید
            if ($modèle->error) {
              print <<<EOT
            <div class="row">                
                <div class="col-md-9">
                    <div class="alert alert-danger" role="alert">
                      Les erreurs suivantes se sont produites :
                      <ul>$modèle->erreurs</ul>
                    </div>
                </div>
            </div>
EOT;
            }
            ?>
        </div>
    </body>
</html>

نظرات

  • خط ۷: یک سند HTML با این خط آغاز می‌شود؛
  • خطوط ۸–۴۴: صفحه HTML در داخل تگ‌های <html> و </html> قرار دارد؛
  • خطوط ۹–۱۶: بخش head سند HTML؛
  • خط ۱۱: تگ <meta charset> نشان می‌دهد که سند در UTF-8 رمزگذاری شده است؛
  • خط ۱۲: تگ <meta name='viewport'> نمایش اولیهٔ نما را تعیین می‌کند: در عرض کامل صفحه‌ای که آن را نمایش می‌دهد (width) در اندازهٔ اصلی‌اش (initial-scale) بدون تغییر اندازه برای جا گرفتن در صفحه‌ای کوچکتر (shrink-to-fit);
  • خط ۱۴: تگ <link rel='stylesheet'> فایل CSS را مشخص می‌کند که ظاهر نما را کنترل می‌کند. در اینجا از چارچوب CSS Bootstrap 4.1.3 [https://getbootstrap.com/docs/4.0/getting-started/introduction/] استفاده می‌کنیم. ;
  • خط ۱۵: تگ <title> عنوان صفحه را تعیین می‌کند:

Image

  • خطوط ۱۷–۴۳: بدنه صفحه وب بین تگ‌های <body> و </body> قرار گرفته است؛
  • خطوط ۱۸–۴۲: تگ یک بخش از صفحه نمایش داده شده را مشخص می‌کند. ویژگی‌های [class] که در view all استفاده شده‌اند، همگی به چارچوب CSS Bootstrap اشاره دارند. تگ <div class='container'> یک کانتینر Bootstrap را مشخص می‌کند؛
  • خط ۲۰: اسکریپت [v-bandeau.php] وارد شده است. این اسکریپت هدر [1] صفحه را تولید می‌کند. به زودی این مورد را توضیح خواهیم داد؛
  • خطوط 22–26: تگ <div class='row'> یک ردیف Bootstrap را تعریف می‌کند. این ردیف‌ها از 12 ستون تشکیل شده‌اند؛
  • خط ۲۳: تگ <div class='col-md-9'> یک بخش با ۹ ستون را تعریف می‌کند؛
  • خط ۲۴: اسکریپت [v-authentification.php] فراخوانی شده است که فرم احراز هویت [2] را در صفحه نمایش می‌دهد. به زودی این مورد را توضیح خواهیم داد؛
  • خط ۲۷: تگ <?php کد PHP را در صفحه HTML درج می‌کند. این کد قبل از نمایش صفحه HTML اجرا می‌شود و می‌تواند آن را تغییر دهد؛
  • خط ۲۹: تمام داده‌های پویا در نمای نمایش‌داده‌شده در یک شیء [$modèle] از نوع [stdClass] قرار داده خواهند شد. این یک انتخاب دلخواه است. می‌توانست به جای آن از یک آرایهٔ asociative برای دستیابی به همان نتیجه استفاده شود؛
  • خط ۲۹: احراز هویت در صورت وارد کردن نام کاربری و رمز عبور نادرست توسط کاربر، ناموفق خواهد بود. در این صورت، نمای احراز هویت همراه با یک پیام خطا مجدداً نمایش داده می‌شود. ویژگی [$modèle→error] مشخص می‌کند که آیا این پیام خطا باید نمایش داده شود یا خیر؛
  • خطوط ۳۰–۳۹: این نحو تمام متن بین نمادهای PHP <<<EOT (خط ۳۰ – شما می‌توانید هر متنی را که می‌خواهید به جای EOT=End Of Text وارد کنید) و نماد EOT در خط ۳۹ (باید با نمادی که در خط ۳۰ استفاده شده یکسان باشد). این نماد باید در ستون اول خط ۳۹ نوشته شود. متغیرهای PHP که در متن بین دو نماد EOT قرار دارند، تفسیر می‌شوند؛
  • خطوط ۳۳–۳۶: ناحیه‌ای با پس‌زمینه صورتی (class="alert alert-danger") تعریف می‌کنند (خط ۳۳);

Image

  • خط ۳۴: متن؛
  • خط ۳۵: تگ HTML (فهرست غیر مرتب) یک فهرست نقطه‌دار را نمایش می‌دهد. هر مورد فهرست باید دارای نحوی item باشد؛

بیایید عناصر پویایی را که باید در این کد تعریف شوند، مشخص کنیم:

  • [$modèle→error]: برای نمایش یک پیام خطا؛
  • [$modèle→erreurs]: فهرستی (در معنای HTML) از پیام‌های خطا؛

23.13.2.2. قطعه [v-bandeau.php]

قطعه [v-bandeau.php] بنر بالایی را در تمام نماهای برنامه وب نمایش می‌دهد:

Image

کد قطعه [v-bandeau.php] به شرح زیر است:


<!--بوت‌استرپ جامبوترون -->
<div class="jumbotron">
    <div class="row">
        <div class="col-md-4">
            <img src="<?= $logo ?>" alt="Cerisier en fleurs" />
        </div>
        <div class="col-md-8">
            <h1>
                Calculez votre impôt
            </h1>
        </div>
    </div>
</div>

نظرات

  • رده‌های ۲ تا ۱۳: بنر در یک بخش Jumbotron بوت‌استرپ ([<div class="jumbotron">]) قرار گرفته است. این کلاس بوت‌استرپ محتوای نمایش‌داده‌شده را به شیوه‌ای خاص استایل می‌دهد تا برجسته شود؛
  • خطوط ۳–۱۲: یک ردیف Bootstrap؛
  • خطوط ۴–۶: یک تصویر [img] در چهار ستون اول ردیف قرار داده شده است؛
  • خط ۵: نحو [<?= $logo ?>] معادل نحو [<?php print $logo ?>] است. به عبارت دیگر، مقدار ویژگی [src] برابر با مقدار متغیر PHP [$logo] خواهد بود؛
  • رده‌های ۷–۱۱: ۸ ستون دیگر در این ردیف (توجه داشته باشید که در مجموع ۱۲ ستون وجود دارد) برای نمایش متن (رده‌ی ۹) با قلم درشت (، رده‌های ۸–۱۰) استفاده خواهند شد؛

عناصر پویا:

  • [$logo]: URL از تصویری که در بنر نمایش داده شده است؛

23.13.2.3. قطعه [v-authentification.php]

قطعه [v-authentification .php] فرم ورود وب‌اپلیکیشن را نمایش می‌دهد:

Image

کد قطعه [v-authentification.php] به شرح زیر است:


<!-- فرم HTML – مقادیر با استفاده از اقدام [authentifier-utilisateur] ارسال می‌شوند -->
<form method="post" action="main.php?action=authentifier-utilisateur">

    <!-- عنوان -->
    <div class="alert alert-primary" role="alert">
        <h4>Veuillez vous authentifier</h4>
    </div>

    <!-- فرم بوت‌استرپ -->
    <fieldset class="form-group">
        <!-- خط اول -->
        <div class="form-group row">
            <!-- برچسب -->
            <label for="user" class="col-md-3 col-form-label">Nom d'utilisateur</label>
            <div class="col-md-4">
                <!-- میدان ورودی متن -->
                <input type="text" class="form-control" id="user" name="user"
                       placeholder="Nom d'utilisateur" value="<?= $modèle->login ?>">
            </div>
        </div>
        <!-- خط دوم -->
        <div class="form-group row">
            <!-- برچسب -->
            <label for="password" class="col-md-3 col-form-label">Mot de passe</label>
            <!-- میدان ورودی متن -->
            <div class="col-md-4">
                <input type="password" class="form-control" id="password" name="password"
                       placeholder="Mot de passe">
            </div>
        </div>
        <!-- دکمه از نوع [submit] در خط سوم-->
        <div class="form-group row">
            <div class="col-md-2">
                <button type="submit" class="btn btn-primary">Valider</button>
            </div>
        </div>
    </fieldset>

</form>

نظرات

  • خطوط ۲–۳۹: تگ <form> یک فرم HTML را مشخص می‌کند. این معمولاً دارای ویژگی‌های زیر است:
    • میدان‌های ورودی را تعریف می‌کند (برچسب‌های <input> در خطوط 17 و 27)؛
    • دارای دکمه‌ای با نوع [submit] (خط ۳۴) است که مقادیر واردشده را به URL مشخص‌شده در ویژگی [action] تگ [form] ارسال می‌کند (خط ۲). متد HTTP که برای پرس‌وجو از این URL استفاده می‌شود، در ویژگی [method] تگ [form] مشخص شده است (خط ۲);
    • در اینجا، هنگامی که کاربر روی دکمه [Valider] (خط ۳۴) کلیک می‌کند، مرورگر مقادیر وارد شده در فرم را ارسال (خط ۲) به URL [main.php?action=authentifier-utilisateur] (خط ۲) خواهد کرد؛
    • مقادیر ارسال‌شده همان‌هایی هستند که کاربر در فیلدهای ورودی در خطوط 17 و 27 وارد کرده است. این مقادیر در فرم [user=xx&password=yy] ارسال خواهند شد. نام‌های پارامترهای [user, passwordنام‌های ویژگی‌های [nameفیلدهای ورودی در خطوط 17 و 27 هستند؛
  • خطوط ۵–۷: یک بخش Bootstrap برای نمایش یک عنوان روی پس‌زمینه آبی:

Image

  • خطوط ۱۰–۳۷: یک فرم Bootstrap. سپس تمام عناصر فرم به شیوه‌ای خاص استایل داده خواهند شد؛
  • خطوط ۱۲–۲۰: تعریف اولین خط فرم:

Image

  • خط ۱۴ برچسب [1] را در سه ستون تعریف می‌کند. ویژگی [for] تگ [label]، برچسب را به ویژگی [id] فیلد ورودی در خط 17 پیوند می‌دهد؛
  • خطوط ۱۵–۱۹: فیلد ورودی را در یک چیدمان چهار ستونی قرار می‌دهد؛
  • خط 17: تگ HTML [input] یک فیلد ورودی را توصیف می‌کند. این تگ چندین پارامتر دارد:
    • [type=’text’]: این یک فیلد ورودی متنی است. می‌توانید هر چیزی را در آن تایپ کنید؛
    • [class=’form-control’]: سبک Bootstrap برای فیلد ورودی؛
    • [id=’user’]: شناسه‌ی فیلد ورودی. این شناسه معمولاً توسط CSS و کد جاوااسکریپت استفاده می‌شود؛
    • [name=’user’]: نام فیلد ورودی متن. مقداری که کاربر وارد می‌کند توسط مرورگر با این نام ارسال خواهد شد؛
    • [placeholder=’invite’]: متنی که هنگام وارد نکردن هیچ‌چیز توسط کاربر در فیلد ورودی نمایش داده می‌شود؛

Image

  • [value=’valeur’]: متن 'value' به محض ظاهر شدن در فیلد ورودی نمایش داده می‌شود، یعنی قبل از اینکه کاربر چیز دیگری وارد کند. این مکانیزم در صورت بروز خطا برای نمایش ورودی‌ای که باعث خطا شده است، استفاده می‌شود. در اینجا، این مقدار، مقدار متغیر PHP [$modèle→login] خواهد بود؛
  • خطوط ۲۱–۳۰: کدی مشابه برای ورود رمز عبور؛
  • خط ۲۷: [type=’password’] تضمین می‌کند که یک فیلد ورودی متنی وجود دارد (می‌توانید هر چیزی تایپ کنید) اما کاراکترهای تایپ‌شده پنهان هستند:

Image

  • خطوط ۳۲–۳۶: یک خط سوم برای دکمه [Valider];
  • خط ۳۴: به دلیل داشتن ویژگی [type=submit]، کلیک روی این دکمه باعث می‌شود مرورگر مقادیر واردشده را به سرور ارسال کند، همان‌طور که قبلاً توضیح داده شد. ویژگی CSS [class="btn btn-primary"] یک دکمه آبی را نمایش می‌دهد:

Image

یک نکته پایانی برای توضیح وجود دارد. در خط ۲، ویژگی [action="main.php?action=authentifier-utilisateur"] یک URL ناقص را تعریف می‌کند (این ویژگی با http://machine:port/chemin شروع نمی‌شود). در مثال ما، تمام ورودی‌های URL در برنامه به شکل [http://localhost/php7/scripts-web/impots/version-12/main.php?action=xx] هستند. نمای احراز هویت با استفاده از ورودی‌های مختلف URL تولید خواهد شد:

  • [http://localhost/php7/scripts-web/impots/version-12/main.php?action=init-session&type=html];
  • [http://localhost/php7/scripts-web/impots/version-12/main.php?action=authentifier-utilisateur]

این URL به سند [main.php] در مسیر [http://localhost/php7/scripts-web/impots/version-12] اشاره دارد. این امر برای تمام URL در این برنامه صدق می‌کند. پارامتر [action="main.php?action=authentifier-utilisateur"] هنگام ارسال مقادیر واردشده با این مسیر پیش‌وند خواهد شد. بنابراین این مقادیر به URL و [http://localhost/php7/scripts-web/impots/version-12/main.php?action=authentifier-utilisateur] ارسال خواهند شد.

23.13.2.4. آزمون بصری

ویوها را می‌توان بسیار پیش از آنکه در برنامه یکپارچه شوند، آزمایش کرد. هدف در اینجا، آزمایش ظاهر بصری آن‌هاست. ما تمام ویوهای آزمایشی را در پوشه [Tests] پروژه گردآوری خواهیم کرد:

Image

برای آزمایش نما [vue-authentification.php]، باید مدل داده‌ای را که نمایش خواهد داد، ایجاد کنیم:


<?php
// داده‌های آزمایشی صفحه
//
// قالب نما محاسبه می‌شود
$modèle = getModelForThisView();

function getModelForThisView(): object {
  //داده‌های صفحه در $modèle جاسازی شده‌اند
  $modèle = new \stdClass();
  //شناسهٔ کاربر
  $modèle->login = "albert";
  // فهرست خطاها
  $modèle->error = TRUE;
  $erreurs = ["erreur1", "erreur2"];
  //یک فهرست از خطاها ساخته می‌شود HTML
  $content = "";
  foreach ($erreurs as $erreur) {
    $content .= "<li>$erreur</li>";
  }
  $modèle->erreurs = $content;
  //تصویر بنر
  $modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
  //قالب رندر شده است
  return $modèle;
}
?>

<!--HTML سند -->
<!doctype html>
<html lang="fr">
    <head>
        <!-- برچسب‌های متای مورد نیاز -->

    </head>
    <body>
        ….
    </body>
</html>

توضیحات

  • خطوط ۱–۵: نمای احراز هویت دارای بخش‌های پویایی است که توسط شیء [$modèle] کنترل می‌شوند. این شیء «مدل نما» نامیده می‌شود. بر اساس یکی از دو تعریف ارائه‌شده برای مخفف MVC، این «M» در MVC است؛
  • خط ۵: قالب نما توسط تابع [getModelForThisView] محاسبه می‌شود؛
  • خط ۹: مدل نما در یک نوع [stdClass] قرار داده خواهد شد؛
  • خطوط ۱۰–۲۲: مقادیر آزمایشی برای عناصر پویا در نمای احراز هویت تعریف شده‌اند؛

آزمون بصری را می‌توان از NetBeans اجرا کرد:

Image

ما این تست‌های بصری را تا زمانی که از نتیجه راضی باشیم ادامه می‌دهیم.

23.13.2.5. محاسبه مدل نما

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


"vues": {
        "vue-authentification.php": [700, 221, 400],
        "vue-calcul-impot.php": [200, 300, 341, 350, 800],
        "vue-liste-simulations.php": [500, 600]
    },
"vue-erreurs": "vue-erreurs.php"

بنابراین، این کدهای وضعیت [700, 221, 400] هستند که باعث نمایش نمای احراز هویت می‌شوند. برای تعیین معنای این کدها، می‌توانیم به تست‌های [Postman] انجام‌شده روی برنامه jSON مراجعه کنیم:

  • [init-session-json-700]: 700 کد وضعیت پس از یک اقدام موفق [init-session] است: سپس فرم احراز هویت خالی نمایش داده می‌شود؛
  • [authentifier-utilisateur-221]: 221 کد وضعیت پس از یک اقدام ناموفق [authentifier-utilisateur] (اطلاعات شناسایی معتبر شناخته نشد): سپس فرم احراز هویت نمایش داده می‌شود تا جزئیات اصلاح شوند؛
  • [fin-session-400]: 400 کد وضعیت پس از یک اقدام موفق [fin-session] است: سپس فرم احراز هویت خالی نمایش داده می‌شود؛

اکنون که می‌دانیم فرم احراز هویت باید چه زمانی نمایش داده شود، می‌توانیم قالب آن را در [vue-authentification.php] محاسبه کنیم:

Image

کد محاسبهٔ قالب نما [vue-authentification.php] به شرح زیر است:


<?php
// متغیرهای زیر ارث برده شده‌اند
// درخواست $request: درخواست فعلی
// جلسه $session: جلسه برنامه
// آرایه $config: پیکربندی برنامه
// آرایه $content: پاسخ کنترلر
//
//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

// قالب نما محاسبه می‌شود
$modèle = getModelForThisView($request, $session, $config, $content);

function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
  // دربرگیری داده‌های صفحه در $modèle
  $modèle = new stdClass();
  //وضعیت برنامه
  $état = $content["état"];
  //مدل به وضعیت وابسته است
  switch ($état) {
    case 700:
    case 400:
      // موردی که فرم خالی نمایش داده می‌شود
      $modèle->login = "";
      // خطایی برای نمایش وجود ندارد
      $modèle->error = FALSE;
      break;
    case 221:
      // احراز هویت ناموفق بود
      // اطلاعات کاربری که در ابتدا وارد شده بود دوباره نمایش داده می‌شود
      $modèle->login = $request->request->get("user");
      // یک خطا برای نمایش وجود دارد
      $modèle->error = TRUE;
      // فهرست پیام‌های خطا (HTML) – فقط یکی در اینجا
      $modèle->erreurs = "<li>Echec de l'authentification</li>";
  }
  // نتیجه
  return $modèle;
}
?>

<!--سند HTML -->
<!doctype html>
<html lang="fr">
    <head>
        
    </head>
    <body>
        
    </body>
</html>

توضیحات

  • خطوط ۳–۶: متغیرهای ارث‌بری‌شده از کلاس [HtmlResponse] اعلام شده‌اند؛ این کلاس باعث می‌شود [require] نما [vue-authentification.php] را نمایش دهد؛
  • خطوط ۹–۱۰: کلاس‌های Symfony مورد استفاده در کد ویو؛
  • خطوط ۱۵–۴۰: تابع [getModelForThisView] مسئول محاسبهٔ قالب نما است؛
  • خط ۱۹: کد وضعیت بازگردانده شده توسط کنترلری که اقدام فعلی را پردازش کرده است، بازیابی می‌شود؛
  • خطوط ۲۱–۳۷: قالب به این کد وضعیت بستگی دارد؛
  • خطوط ۲۲–۲۸: حالتی که باید یک فرم احراز هویت خالی نمایش داده شود؛
  • خطوط ۲۹–۳۷: در صورت ناموفق بودن تلاش احراز هویت: نام کاربری واردشده توسط کاربر به همراه یک پیام خطا نمایش داده می‌شود. کاربر سپس می‌تواند با نام کاربری دیگری دوباره تلاش کند؛

قالب مشخصی برای بنر [v-bandeau.php] نوشته شده است:


<?php
  // لوگو
  $scheme = $request->server->get('REQUEST_SCHEME'); // http
  $host = $request->server->get('SERVER_NAME'); // localhost
  $port = $request->server->get('SERVER_PORT'); // 80
  $uri = $request->server->get('REQUEST_URI'); // /php7/scripts-web/impots/version-12/main.php?action=xxx
  $champs = [];
  preg_match("/(.+)\/.+?$/", $uri, $champs);
  $root = $champs[1]; // /php7/scripts-web/impots/version-12
  $modèle->logo = "$scheme://$host:$port$root/Views/logo.jpg"; // http://localhost:80/php7/scripts-web/impots/version-12/Views/logo.jpg
?>
<!-- بوت‌استرپ جامبوترون -->
<div class="jumbotron">
    <div class="row">
        <div class="col-md-4">
            <img src="<?= $modèle->logo ?>" alt="Cerisier en fleurs" />
        </div>
        <div class="col-md-8">
            <h1>
                Calculez votre impôt
            </h1>
        </div>
    </div>
</div>

توضیحات

  • خط ۱۶ از متغیر [$modèle→logo] استفاده می‌کند که برای لوگوی بنر، URL است. به جای محاسبه این متغیر چهار بار برای چهار نمای برنامه، این محاسبه به قطعه [v-bandeau.php] منتقل شده است؛
  • خطوط ۱ تا ۱۱ نشان می‌دهند که چگونه URL و [http://localhost:80/php7/scripts-web/impots/version-12/Views/logo.jpg] را از اطلاعاتی که در محیط سرور [$request→server] یافت می‌شود، بسازیم؛

23.13.2.6. آزمون‌های [Postman]

ما قبلاً درخواست‌هایی ایجاد کرده‌ایم که کدهای [700, 221, 400] را تولید می‌کنند و نمای احراز هویت را نمایش می‌دهند. برای خلاصه:

  • [init-session-html-700]: 700 کد وضعیت پس از یک اقدام موفق [init-session] است: سپس فرم احراز هویت خالی نمایش داده می‌شود؛
  • [authentifier-utilisateur-221]: 221 کد وضعیت پس از یک اقدام ناموفق [authentifier-utilisateur] (اطلاعات شناسایی نامعتبر) است: سپس فرم احراز هویت نمایش داده می‌شود تا جزئیات اصلاح شوند؛
  • [fin-session-400]: 400 کد وضعیت پس از یک اقدام موفق [fin-session] است: سپس فرم احراز هویت خالی نمایش داده می‌شود؛

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

  • [init-session-html-700]: شروع یک جلسه HTML؛

Image

  • [authentifier-utilisateur-221]: احراز هویت کاربر [x, x];

Image

بالا:

  • درخواست رشته [user=x&password=x] را ارسال کرده بود؛
  • در [4]، یک پیام خطا نمایش داده می‌شود؛
  • در [3]، کاربر نادرست دوباره نمایش داده شد؛

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

ما توانستیم نمای [vue-authentification.php] را بدون نوشتن سایر نماها آزمایش کنیم. این امر ممکن بود زیرا:

  • تمام کنترلرها نوشته شده‌اند؛
  • [Postman] به ما امکان می‌دهد بدون نیاز به ویوها، درخواست‌ها را به سرور ارسال کنیم. هنگام نوشتن کنترلرها، باید آگاه باشید که هر کسی می‌تواند این کار را انجام دهد. بنابراین باید برای رسیدگی به درخواست‌هایی که هیچ ویوی (view) آن‌ها را مجاز نمی‌داند، آماده باشیم. این درخواست‌ها به‌صورت دستی در [Postman] ایجاد می‌شوند. هرگز نباید پیش‌فرض کنیم که «این درخواست غیرممکن است». باید بررسی کنیم؛

23.13.3. نما نمایش محاسبه مالیات

23.13.3.1. نمای کلی ویو

نمایش محاسبه مالیات به شرح زیر است:

Image

این نما از سه بخش تشکیل شده است:

  • ۱: بنر بالایی توسط قطعه [v-bandeau.php] تولید می‌شود که قبلاً توضیح داده شده است؛
  • ۲: فرم محاسبه مالیات، تولید شده توسط قطعه [v-calcul-impot.php]؛
  • ۳: منویی شامل دو پیوند، تولید شده توسط قطعه [v-menu.php];

نمایه محاسبه مالیات توسط اسکریپت زیر، [vue-calcul-impot.php تولید می‌شود:

Image


<?php
// متغیرهای زیر ارث برده شده‌اند
// درخواست $request: درخواست فعلی
// Session $session: جلسهٔ برنامه
// آرایه $config: پیکربندی برنامه
// آرایه $content: پاسخ از کنترلری که اقدام را مدیریت کرد
//
//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

// قالب نما محاسبه می‌شود
$modèle = getModelForThisView($request, $session, $config, $content);

function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
  // داده‌های صفحه در $modèle محصور شده است
  $modèle = new \stdClass();

  //مدل رندر می‌شود
  return $modèle;
}
?>
<!--سند HTML -->
<!doctype html>
<html lang="fr">
    <head>
        <!-- برچسب‌های متای مورد نیاز -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!--بوت‌استرپ CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
        <title>Application impots</title>
    </head>
    <body>
        <div class="container">
            <!-- سربرگ -->
            <?php require "v-bandeau.php"?>
            <!-- قالب دو ستونی -->
            <div class="row">
                <!-- منو -->
                <div class="col-md-3">
                    <?php require "v-menu.php" ?>
                </div>
                <!-- فرم محاسبه -->
                <div class="col-md-9">
                    <?php require "v-calcul-impot.php" ?>
                </div>
            </div>  
            <!-- مورد موفقیت -->
            <?php
            if ($modèle->success) {
              // یک هشدار موفقیت نمایش داده می‌شود
              print <<<EOT1
            <div class="row">
                <div class="col-md-3">

                </div>
                <div class="col-md-9">
                    <div class="alert alert-success" role="alert">
                        $modèle->impôt</br>
                        $modèle->décôte</br>\n
                        $modèle->réduction</br>\n
                        $modèle->surcôte</br>\n
                        $modèle->taux</br>\n
                    </div>
                </div>
            </div>
EOT1;
            }
            ?>
            <?php
            if ($modèle->error) {
              // فهرست ۹ ستونی خطاها
              print <<<EOT2
                <div class="row">
                  <div class="col-md-3">

                  </div>
                  <div class="col-md-9">
                      <div class="alert alert-danger" role="alert">
                        L'erreur suivante s'est produite :
                        <ul>$modèle->erreurs</ul>
                      </div>
                  </div>
                </div>
EOT2;
            }
            ?>
        </div>
    </body>
</html>

نظرات

  • ما تنها درباره ویژگی‌های جدیدی که هنوز با آن‌ها مواجه نشده‌ایم، نظر می‌دهیم؛
  • خط ۳۷: درج بنر بالایی نما در ردیف اول بوت‌استرپ نما؛
  • خطوط ۴۱–۴۳: درج منو، که سه ستون از ردیف دوم Bootstrap نما را اشغال خواهد کرد؛
  • خطوط ۴۵–۴۷: درج فرم محاسبه مالیات، که نه ستون از ردیف دوم Bootstrap نما را اشغال خواهد کرد؛
  • خطوط ۵۱–۶۹: اگر محاسبه مالیات با موفقیت انجام شود ([$modèle→success=TRUE])، نتیجه محاسبه مالیات در یک کادر سبز نمایش داده می‌شود (خطوط ۵۹–۶۵). این کادر در سومین ردیف بوت‌استرپ نما (خط ۵۴) قرار دارد و نه ستون (خط ۵۸) را در سمت راست سه ستون خالی (خطوط ۵۵–۵۷) اشغال می‌کند. بنابراین این کادر بلافاصله زیر فرم محاسبه مالیات قرار خواهد گرفت؛
  • ردیف‌های ۷۱–۸۷: اگر محاسبه مالیات برای [$modèle→error=TRUE] ناموفق باشد، یک پیام خطا در یک کادر صورتی (ردیف‌های ۸۰–۸۳) نمایش داده می‌شود. این قاب در سومین سطر بوت‌استرپِ نما (خط ۷۵) قرار دارد و نه ستون (خط ۷۹) را در سمت راست سه ستون خالی (خطوط ۷۶–۷۸) اشغال می‌کند. بنابراین این قاب بلافاصله در زیر فرم محاسبه مالیات قرار خواهد گرفت؛

23.13.3.2. قطعه [v-calcul-impot.php]

قطعه [v-calcul-impot.php] فرم احراز هویت برنامه وب را نمایش می‌دهد:

Image

کد قطعه [v-calcul-impot.php] به شرح زیر است:


<!-- HTML فرم ارسال شد -->
<form method="post" action="main.php?action=calculer-impot">
    <!-- پیامی در ۱۲ ستون روی پس‌زمینه آبی -->
    <div class="col-md-12">
        <div class="alert alert-primary" role="alert">
            <h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
        </div>
    </div>
    <!-- میدان‌های فرم -->
    <fieldset class="form-group">
        <!-- اولین ردیف با ۹ ستون -->
        <div class="row">
            <!--متن در ۴ ستون -->
            <legend class="col-form-label col-md-4 pt-0">Etes-vous marié(e) ou pacsé(e)?</legend>
            <!-- دکمه‌های رادیویی در ۵ ستون-->
            <div class="col-md-5">
                <div class="form-check">
                    <input class="form-check-input" type="radio" name="marié" id="gridRadios1" value="oui" <?= $modèle->checkedOui ?>>
                    <label class="form-check-label" for="gridRadios1">
                        Oui
                    </label>
                </div>
                <div class="form-check">
                    <input class="form-check-input" type="radio" name="marié" id="gridRadios2" value="non" <?= $modèle->checkedNon ?>>
                    <label class="form-check-label" for="gridRadios2">
                        Non
                    </label>
                </div>
            </div>
        </div>
        <!-- ردیف دوم، ۹ ستون -->
        <div class="form-group row">
            <!-- متن در ۴ ستون -->
            <label for="enfants" class="col-md-4 col-form-label">Nombre d'enfants à charge</label>
            <!-- میدان ورودی عددی برای تعداد فرزندان، ۵ ستون -->
            <div class="col-md-5">
                <input type="number" min="0" step="1" class="form-control" id="enfants" name="enfants" placeholder="Nombre d'enfants à charge" value="<?= $modèle->enfants ?>">
            </div>
        </div>
        <!-- ردیف سوم، ۹ ستون -->
        <div class="form-group row">
            <!-- میدان متنی در عرض ۴ ستون -->
            <label for="salaire" class="col-md-4 col-form-label">Salaire annuel</label>
            <!-- میدان ورودی عددی برای حقوق، ۵ ستون -->
            <div class="col-md-5">
                <input type="number" min="0" step="1" class="form-control" id="salaire" name="salaire" placeholder="Salaire annuel" aria-describedby="salaireHelp" value="<?= $modèle->salaire ?>">
                <small id="salaireHelp" class="form-text text-muted">Arrondissez à l'euro inférieur</small>
            </div>
        </div>
        <!-- ردیف چهارم، دکمه [submit] در عرض ۵ ستون -->
        <div class="form-group row">
            <div class="col-md-5">
                <button type="submit" class="btn btn-primary">Valider</button>
            </div>
        </div>
    </fieldset>

</form>

نظرات

  • خط ۲: فرم HTML ارسال خواهد شد (ویژگی [method]) به URL [main.php?action=calculer-impot] (ویژگی [action]). مقادیر ارسال‌شده، مقادیر فیلدهای ورودی خواهند بود:
    • مقدار دکمه رادیویی انتخاب‌شده در فرم:
      • [marié=oui] اگر دکمهٔ رادیویی [Oui] انتخاب شده باشد (خطوط 16–22). [marié] مقدار ویژگی [name] در خط ۱۸ است، [oui] مقدار ویژگی [value] در خط ۱۸ است؛
      • [marié=non] اگر دکمهٔ رادیویی [Non] انتخاب شده باشد (خطوط 23–28). [marié] مقدار ویژگی [name] در سطر 24 است، و [non] مقدار ویژگی [value] در سطر 24 است؛
    • مقدار در فیلد ورودی عددی در خط ۳۷ در فرم [enfants=xx]، که در آن [enfants] مقدار ویژگی [name] در خط ۳۷ است، و [xx] مقداری است که توسط کاربر از طریق صفحه‌کلید وارد شده است؛
    • مقدار فیلد ورودی عددی در خط ۴۶ در فرم [salaire=xx]، که در آن [salaire] مقدار ویژگی [name] در خط ۴۶ است، و [xx] مقداری است که کاربر از طریق صفحه‌کلید وارد کرده است؛

در نهایت، مقدار ارسال‌شده در قالب [marié=xx&enfants=yy&salaire=zz] خواهد بود.

  • مقادیر وارد شده هنگام کلیک کاربر روی دکمه با نوع [submit] در خط 53 ارسال خواهند شد؛
  • خطوط ۱۶–۳۰: دو دکمهٔ رادیویی:

Image

این دو دکمه رادیویی بخشی از یک گروه دکمه رادیویی واحد هستند زیرا ویژگی یکسانی به نام [name] (خطوط ۱۸ و ۲۴) را به اشتراک می‌گذارند. مرورگر تضمین می‌کند که در یک گروه دکمه رادیویی، در هر زمان تنها یکی از آنها انتخاب باشد. بنابراین، کلیک کردن روی یکی، دیگری را که قبلاً انتخاب شده بود، غیرفعال می‌کند؛

  • آنها به دلیل ویژگی [type="radio"] (خطوط ۱۸ و ۲۴) دکمه رادیویی هستند؛
  • هنگامی که فرم نمایش داده می‌شود (قبل از هرگونه ورودی)، یکی از دکمه‌های رادیویی باید انتخاب شده باشد: برای این کار، کافی است ویژگی [checked=’checked’] را به تگ مربوطه <input type="radio"> اضافه کنید. این کار با استفاده از متغیرهای پویا انجام می‌شود:
    • [<?= $modèle->checkedOui ?>] در خط ۱۸؛
    • [<?= $modèle->checkedNon ?>] در خط ۲۴؛

این متغیرها بخشی از قالب نما را تشکیل خواهند داد.

  • خط ۳۷: یک فیلد ورودی عددی [type="number"] با حداقل مقدار 0 [min="0"]. در مرورگرهای مدرن، این بدان معناست که کاربر تنها می‌تواند عددی بزرگ‌تر یا مساوی صفر وارد کند. در همین مرورگرهای مدرن، می‌توان ورودی را با استفاده از یک اسلایدر انجام داد که با کلیک روی آن می‌توان مقدار را افزایش یا کاهش داد. ویژگی [step="1"] در خط ۳۷ نشان می‌دهد که اسلایدر به صورت گام‌های ۱ واحدی عمل خواهد کرد. در نتیجه، اسلایدر فقط مقادیر صحیح در بازه ۰ تا n را با گام‌های ۱ واحدی می‌پذیرد. برای ورود دستی، این بدان معناست که اعداد دارای ممیز اعشاری پذیرفته نخواهند شد؛

Image

  • خط ۳۷: در برخی از صفحات، فیلد ورودی فرزندان باید با آخرین ورودی ثبت‌شده در آن فیلد، از پیش پر شود. برای انجام این کار، از ویژگی [value] استفاده می‌کنیم که مقداری را که باید در فیلد ورودی نمایش داده شود، تعیین می‌کند. این مقدار پویا بوده و توسط متغیر [$modèle→enfants] تولید می‌شود؛
  • خط ۴۶: توضیحات مشابهی برای ورود حقوق نیز مانند موارد مربوط به کودکان اعمال می‌شود؛
  • خط ۵۳: دکمه‌ای از نوع [submit]، که باعث می‌شود POST فیلدهای URL و [main.php?action=calculer-impot] را با مقادیر وارد شده پر کند؛

Image

23.13.3.3. قطعه [v-menu.php]

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

Image

کد این قطعه به شرح زیر است:


<!--منوی بوت‌استرپ -->
<nav class="nav flex-column">
    <?php
    //نمایش فهرستی از لینک‌ها HTML
    foreach($modèle->optionsMenu as $texte=>$url){
      print <<<EOT3
      <a class="nav-link" href="$url">$texte</a>
EOT3;
    }
    ?>
</nav>

نظرات

  • خطوط ۲–۱۱: تگ HTML [nav] بخشی از سند HTML را که حاوی پیوندهای ناوبری به اسناد دیگر است، در بر می‌گیرد؛
  • خط ۷: تگ HTML [a] یک لینک ناوبری را معرفی می‌کند:
    • [$url]: همان URL است که کاربر با کلیک بر روی لینک [$texte] به آن هدایت می‌شود. این سپس عملیاتی [GET $url] است که توسط مرورگر انجام می‌شود. اگر [$url] یک URL نسبی باشد، آنگاه ریشهٔ URL که در حال حاضر در نوار آدرس مرورگر نمایش داده می‌شود، به ابتدای آن افزوده می‌شود. بنابراین، برای دریافت لینک [1]، در حالی که URL فعلی مرورگر از نوع [http://chemin/main.php?paramètres] است، لینک زیر ایجاد خواهد شد:
<a href=’main.php?action=liste-simulation’>Liste des simulations</a>
  • خط ۵: قالب [$modèle→optionsMenu] این قطعه آرایه‌ای به شکل زیر خواهد بود:
[‘ Liste des simulations’=>’main.php?action=liste-simulations’,
‘ Fin de session’=>’main.php?action=fin-session’]
  • خطوط ۲ و ۷: کلاس‌های CSS و [nav, flex-column, nav-link] کلاس‌های Bootstrap هستند که ظاهر منو را تعریف می‌کنند؛

23.13.3.4. آزمایش بصری

ما این عناصر مختلف را در پوشه [Tests] گروه‌بندی کرده و یک قالب آزمایشی برای نمای [vue-calcul-impot.php] ایجاد می‌کنیم:

Image

مدل داده برای نما [vue-calcul-impot] به شرح زیر خواهد بود:


<?php
// داده‌های آزمایشی صفحه
//
//محاسبهٔ قالب نما
$modèle = getModelForThisView();

function getModelForThisView(): object {
  // دربرگیری داده‌های صفحه در $modèle
  $modèle = new \stdClass();
  //فرم
  $modèle->checkedOui = "";
  $modèle->checkedNon = 'checked="checked"';
  $modèle->enfants = 2;
  $modèle->salaire = 300000;
  // پیام موفقیت
  $modèle->success = TRUE;
  $modèle->impôt = "Montant de l'impôt : 1000 euros";
  $modèle->décôte = "Décôte : 15 euros";
  $modèle->réduction = "Réduction : 20 euros";
  $modèle->surcôte = "Surcôte : 0 euros";
  $modèle->taux = "Taux d'imposition : 14 %";
  // پیام خطا
  $modèle->error = TRUE;
  $erreurs = ["erreur1", "erreur2"];
  //یک فهرست از خطاها تولید می‌شود HTML
  $content = "";
  foreach ($erreurs as $erreur) {
    $content .= "<li>$erreur</li>";
  }
  $modèle->erreurs = $content;
  // منو
  $modèle->optionsMenu = [
    ''فهرست شبیه‌سازی‌ها' => 'main.php?action=list-simulations',
    ''پایان جلسه' => 'main.php?action=end-session'];
  //تصویر بنر
  $modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
  //ارسال فرم
  return $modèle;
}

?>
<!-- HTML سند -->
<!doctype html>
<html lang="fr">
    <head>
        
    </head>
    <body>
        
    </body>
</html>

نظرات

  • خطوط ۷–۳۹: ما تمام بخش‌های پویا از نمای [vue-calcul-impot.php] و قطعات [v-calcul-impot.php] و [v-menu.php] را مقداردهی اولیه می‌کنیم؛

ما نما [vue-calcul-impot.php] را آزمایش می‌کنیم:

Image

نتیجه زیر به دست آمد:

Image

ما به کار روی این نما ادامه می‌دهیم تا زمانی که از نتیجه بصری آن رضایت داشته باشیم. سپس می‌توانیم نما را در وب‌اپلیکیشن در حال توسعه ادغام کنیم.

23.13.3.5. محاسبهٔ قالب نما

Image

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


"vues": {
        "vue-authentification.php": [700, 221, 400],
        "vue-calcul-impot.php": [200, 300, 341, 350, 800],
        "vue-liste-simulations.php": [500, 600]
    },
"vue-erreurs": "vue-erreurs.php"

بنابراین کدهای وضعیت [200, 300, 341, 350, 800] هستند که نمایش صفحهٔ احراز هویت را فعال می‌کنند. برای پی بردن به معنای این کدها، می‌توانیم به آزمون‌های [Postman] انجام‌شده روی برنامهٔ jSON مراجعه کنیم:

  • [authentifier-utilisateur-200]: 200 کد وضعیت پس از یک اقدام موفق [authentifier-itilisateur] است: سپس فرم خالی محاسبه مالیات نمایش داده می‌شود؛
  • [calculer-impot-300]: 300 کد وضعیت پس از یک اقدام موفق [calculer-impot] است. سپس فرم محاسبه نمایش داده می‌شود که داده‌های وارد شده و مبلغ مالیات را نشان می‌دهد. کاربر سپس می‌تواند محاسبه دیگری را انجام دهد؛
  • [fin-session-400]: 400 کد وضعیت پس از یک اقدام موفق [fin-session] است: فرم احراز هویت سپس خالی نمایش داده می‌شود؛
  • کد وضعیت [341] برای یک محاسبه مالیات معتبر بازگردانده می‌شود، اما عدم وجود اتصال به SGBD باعث ایجاد خطا می‌شود؛
  • کد وضعیت [350] برای یک محاسبه مالیات معتبر بازگردانده می‌شود، اما عدم وجود اتصال به سرور [Redis] باعث خطا می‌شود؛
  • کد وضعیت [800] در تاریخ بعدی ارائه خواهد شد. ما هنوز با آن مواجه نشده‌ایم؛
  • ما در اینجا فرض کرده‌ایم که کاربر از یک مرورگر جدید استفاده می‌کند. بنابراین، با فرم مورد نظر، وارد کردن اعداد منفی، رشته‌های کاراکتری غیرعددی یا اعداد اعشاری در فیلدهای ورودی [enfants, salaire] امکان‌پذیر نیست. با مرورگرهای قدیمی‌تر، این کار ممکن بود. ما این خطاها را به‌عنوان خطاهای غیرمنتظره در نظر می‌گیریم و سپس نما [vue-erreurs] را نمایش می‌دهیم؛

اکنون که می‌دانیم فرم محاسبه مالیات چه زمانی باید نمایش داده شود، می‌توانیم قالب آن را در [vue-calcul-impot.php] تعریف کنیم:


<?php
// متغیرهای زیر ارث برده شده‌اند
// درخواست $request: درخواست فعلی
// جلسه $session: جلسه برنامه
// آرایه $config: پیکربندی برنامه
//array $content: پاسخ کنترلری که اقدام را پردازش کرد
//
//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

// قالب نما محاسبه می‌شود
$modèle = getModelForThisView($request, $session, $config, $content);

function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
  // دربرگیری داده‌های صفحه در $modèle
  $modèle = new \stdClass();
  //وضعیت برنامه
  $état = $content["état"];
  //مدل به وضعیت وابسته است
  switch ($état) {
    case 200 :
    case 800:
      //نمایش اولیه یک فرم خالی
      $modèle->success = FALSE; $modèle->errror = FALSE;
      $modèle->checkedNon = 'checked="checked"';
      $modèle->checkedOui = "";
      $modèle->enfants = "";
      $modèle->salaire = "";
      break;
    case 300:
      //محاسبه موفقیت‌آمیز بود – نتیجه نمایش داده شد
      $modèle->success = TRUE;
      $modèle->error = FALSE;
      $modèle->impôt = "Montant de l'impôt : {$content["réponse"]["impôt"]} euros";
      $modèle->décôte = "Décôte : {$content["réponse"]["décôte"]} euros";
      $modèle->réduction = "Réduction : {$content["réponse"]["réduction"]} euros";
      $modèle->surcôte = "Surcôte : {$content["réponse"]["surcôte"]} euros";
      $modèle->taux = "Taux d'imposition : " . ($content["réponse"]["taux"] * 100) . " %";
      // فرم با مقادیر وارد شده بازیابی شد
      $modèle->checkedOui = $request->request->get("marié") === "oui" ? 'checked="checked"' : "";
      $modèle->checkedNon = $request->request->get("marié") === "oui" ? "" : 'checked="checked"';
      $modèle->enfants = $request->request->get("enfants");
      $modèle->salaire = $request->request->get("salaire");
      break;
    case 341:
    //پایگاه داده HS
    case 350:
      // سرور Redis HS
      // فرم با مقادیر وارد شده بازیابی شد
      $modèle->checkedOui = $request->request->get("marié") === "oui" ? 'checked="checked"' : "";
      $modèle->checkedNon = $request->request->get("marié") === "oui" ? "" : 'checked="checked"';
      $modèle->enfants = $request->request->get("enfants");
      $modèle->salaire = $request->request->get("salaire");
      //خطا
      $modèle->success = FALSE;
      $modèle->error = TRUE;
      $modèle->erreurs = "<li>{$content["réponse"]}</li>";
      break;
  }
  //منو
  $modèle->optionsMenu = [
    "Liste des simulations" => "main.php?action=lister-simulations",
    "Fin de session" => "main.php?action=fin-session"];
  //قالب رندر شد
  return $modèle;
}
?>
<!--سند HTML -->
<!doctype html>
<html lang="fr">
    <head>
        
        <title>Application impots</title>
    </head>
    <body>
        
    </body>
</html>

توضیحات

  • رده‌های ۲۲–۳۰: نمایش یک فرم خالی؛
  • خطوط ۳۱–۴۵: محاسبه موفق مالیات. مقادیر وارد شده و مبلغ مالیات دوباره نمایش داده می‌شوند؛
  • خطوط ۴۶–۵۹: محاسبه مالیات ناموفق است زیرا یکی از سرورها ([Redis] یا [MySQL]) در دسترس نیست؛
  • خطوط ۶۲–۶۴: محاسبه دو گزینه منو؛

23.13.3.6. آزمون‌های [Postman]

آزمون [calculer-impot-300] کد وضعیت 300 را برمی‌گرداند. این نشان‌دهنده موفقیت در محاسبه مالیات است:

Image

  • در [3]، مقادیری که منجر به نتیجه [2] شدند؛

بیایید یک سناریوی خطا را امتحان کنیم: خطای [350] به دلیل در دسترس نبودن سرور ([Redis]):

Image

23.13.4. نمايش فهرست شبيه‌سازي

23.13.4.1. نمای کلی نما

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

Image

نما تولیدشده توسط اسکریپت [vue-liste-simulations] از سه بخش تشکیل شده است:

  • ۱: بنر بالایی توسط قطعه [v-bandeau.php] تولید می‌شود که قبلاً توضیح داده شده است؛
  • ۲: جدول شبیه‌سازی‌ها که توسط قطعه [v-liste-simulations.php] تولید شده است؛
  • ۳: یک منو شامل دو لینک، تولید شده توسط قطعه [v-menu.php];

نمایه شبیه‌سازی توسط اسکریپت زیر، [vue-liste-simulations.php تولید می‌شود:

Image


<?php

//محاسبهٔ قالب نما
$modèle = getModelForThisView();

function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
  // داده‌های صفحه را در $modèle محصور کنید
  $modèle = new \stdClass();
  
  //قالب رندر می‌شود
  return $modèle;
}
?>
<!--سند HTML -->
<!doctype html>
<html lang="fr">
    <head>
        <!-- تگ‌های متای مورد نیاز -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!--بوت‌استرپ CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
        <title>Application impots</title>
    </head>
    <body>
        <div class="container">
            <!-- سربرگ -->
            <?php require "v-bandeau.php"; ?>
            <!-- قالب دو ستونی -->
            <div class="row">
                <!-- منوی سه‌ستونی-->
                <div class="col-md-3">
                    <?php require "v-menu.php" ?>
                </div>
                <!-- فهرست ۹ ستونی شبیه‌سازی‌ها-->
                <div class="col-md-9">
                    <?php require "v-liste-simulations.php" ?>
                </div>
            </div>  
        </div>
    </body>
</html>

نظرات

  • خط ۲۸: درج بنر برنامه [1]؛
  • خط ۳۳: درج منوی [2]. این منو در سه ستون زیر بنر نمایش داده خواهد شد؛
  • خط ۳۷: درج جدول شبیه‌سازی [3]. این جدول در نه ستون زیر بنر و در سمت راست منو نمایش داده خواهد شد؛

ما قبلاً دو بخش از سه بخش این نما را مورد بحث قرار داده‌ایم:

قطعه [v-liste-simulations.php] به شرح زیر است:


<!-- پیام روی پس‌زمینه آبی -->
<div class="alert alert-primary" role="alert">
    <h4>Liste de vos simulations</h4>
</div>
<!-- جدول شبیه‌سازی‌ها -->
<table class="table table-sm table-hover table-striped">
    <!-- سربرگ‌های شش ستون جدول -->
    <thead>
        <tr>
            <th scope="col">#
            <th scope="col">Marié</th>
            <th scope="col">Nombre d'enfants</th>
            <th scope="col">Salaire annuel</th>
            <th scope="col">Montant impôt</th>
            <th scope="col">Surcôte</th>
            <th scope="col">Décôte</th>
            <th scope="col">Réduction</th>
            <th scope="col">Taux</th>
            <th scope="col"></th>
        </tr>
    </thead>
    <!-- بدنه جدول (داده‌های نمایش داده شده) -->
    <tbody>
        <?php
        $i = 0;
        // هر شبیه‌سازی با چرخش در جدول شبیه‌سازی نمایش داده می‌شود
        foreach ($modèle->simulations as $simulation) {
          //نمایش یک سطر از جدول با ۶ ستون – تگ <tr>
          //ستون ۱: سربرگ ردیف (شماره شبیه‌سازی) – تگ <th scope='row'>
          //ستون ۲: مقدار پارامتر [marié] – تگ 
          // ستون ۳: مقدار پارامتر [enfants] - تگ 
          // ستون ۴: مقدار پارامتر [salaire] – تگ 
          // ستون ۵: مقدار پارامتر [impôt] (برای مالیات) – تگ 
          //ستون ۶: مقدار پارامتر [surcôte] - تگ 
          //ستون ۷: مقدار پارامتر [décôte] – برچسب 
          //ستون ۸: مقدار پارامتر [réduction] – تگ 
          //ستون ۹: مقدار پارامتر [taux] (برای مالیات) – تگ 
          // ستون ۱۰: لینک حذف شبیه‌سازی - تگ 
          print <<<EOT
        <tr>
          <th scope="row">$i</th>
          <td>{$simulation["marié"]}</td>
          <td>{$simulation["enfants"]}</td>
          <td>{$simulation["salaire"]}</td>
          <td>{$simulation["impôt"]}</td>
          <td>{$simulation["surcôte"]}</td>
          <td>{$simulation["décôte"]}</td>
          <td>{$simulation["réduction"]}</td>
          <td>{$simulation["taux"]}</td>
          <td><a href="main.php?action=supprimer-simulation&numéro=$i">Supprimer</a></td>
        </tr>
EOT;
          $i++;
        }
        ?>
        </tr>
    </tbody>
</table>

نظرات

  • یک جدول HTML با استفاده از تگ (خطوط ۶ و ۵۸) ایجاد می‌شود؛
  • سربرگ‌های ستون جدول درون تگ <thead> (سر جدول، خطوط ۸ و ۲۱) تعریف شده‌اند. تگ <tr> (رد جدول، خطوط ۹ و ۲۰) یک ردیف را جدا می‌کند. خطوط ۱۰–۱۵: تگ (سر جدول) یک سرستون را تعریف می‌کند. بنابراین، تعداد آن‌ها ده تا است. [scope="col"] نشان می‌دهد که سربرگ به ستون اعمال می‌شود. [scope="row"] نشان می‌دهد که سربرگ به سطر اعمال می‌شود؛
  • خطوط ۲۳–۵۷: تگ <tbody> داده‌های نمایش‌داده‌شده توسط جدول را در بر می‌گیرد؛
  • خطوط ۴۰–۵۱: تگ <tr> یک سطر از جدول را در بر می‌گیرد؛
  • خط ۴۱: تگ <th scope='row'> سربرگ سطر را تعریف می‌کند؛
  • خطوط ۴۲–۵۰: هر تگ یک ستون را در داخل سطر تعریف می‌کند؛
  • خط ۲۷: فهرست شبیه‌سازی‌ها را می‌توان در مدل [$modèle→simulations] که یک آرایهٔ انجمنی است، یافت؛
  • خط ۵۰: لینکی برای حذف شبیه‌سازی. مدل URL از عددی که در ستون اول جدول (خط ۴۱) نمایش داده شده است، استفاده می‌کند؛

23.13.4.2. آزمون بصری

ما این عناصر مختلف را در پوشه [Tests] گردآوری می‌کنیم و یک قالب آزمایشی برای نما [vue-liste-simulations.php] ایجاد می‌کنیم:

Image

مدل داده برای نما [vue-liste-simulations] به شرح زیر خواهد بود:


<?php
//قالب نما محاسبه می‌شود
$modèle = getModelForThisView();

function getModelForThisView(): object {
  //داده‌های صفحه در $modèle محصور شده است
  $modèle = new \stdClass();
  //قالب‌بندی شبیه‌سازی‌ها بر اساس فرمت مورد انتظار صفحه
  $modèle->simulations = [
    [
      "marié" => "oui",
      "enfants" => 2,
      "salaire" => 60000,
      "impôt" => 448,
      "décôte" => 100,
      "réduction" => 20,
      "surcôte" => 0,
      "taux" => 0.14
    ],
    [
      "marié" => "non",
      "enfants" => 2,
      "salaire" => 200000,
      "impôt" => 25600,
      "décôte" => 0,
      "réduction" => 0,
      "surcôte" => 8400,
      "taux" => 0.45
    ]
  ];
  // گزینه‌های منو
  $modèle->optionsMenu = [
    "Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
    "Fin de session" => "main.php?action=fin-session"];
  //تصویر بنر
  $modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
  //قالب رندر می‌شود
  return $modèle;
}
?>
<!-- HTML سند -->
<!doctype html>
<html lang="fr">
    <head>
        
    </head>
    <body>
        
    </body>
</html>

نظرات

  • خطوط ۹–۳۰: آرایه شبیه‌سازی‌های نمایش‌داده‌شده توسط جدول HTML;
  • خطوط ۳۲–۳۴: جدول گزینه‌های منو؛

بیایید این نما را نمایش دهیم:

Image

نتیجهٔ زیر به‌دست می‌آید:

Image

ما به کار روی این نما ادامه می‌دهیم تا زمانی که از ظاهر آن راضی باشیم. سپس می‌توانیم به ادغام این نما در اپلیکیشن وبی که در حال حاضر در حال توسعه آن هستیم، بپردازیم.

23.13.4.3. محاسبه مدل نما

Image

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


"vues": {
        "vue-authentification.php": [700, 221, 400],
        "vue-calcul-impot.php": [200, 300, 341, 350, 800],
        "vue-liste-simulations.php": [500, 600]
    },
"vue-erreurs": "vue-erreurs.php"

بنابراین، کدهای وضعیت [500, 600] هستند که باعث نمایش نمای شبیه‌سازی می‌شوند. برای درک معنای این کدها، می‌توانیم به تست‌های [Postman] انجام‌شده روی برنامه jSON مراجعه کنیم:

  • [lister-simulations-500]: ۵۰۰ کد وضعیت پس از یک اقدام موفق [lister-simulations] است: فهرست شبیه‌سازی‌های انجام‌شده توسط کاربر سپس نمایش داده می‌شود؛
  • [supprimer-simulation-600]: 600 کد وضعیت پس از یک عملیات موفق [supprimer-simulation] است. سپس لیست جدید شبیه‌سازی‌ها که پس از این حذف به دست آمده، نمایش داده می‌شود؛

اکنون که می‌دانیم فهرست شبیه‌سازی‌ها باید چه زمانی نمایش داده شود، می‌توانیم مدل آن را در [vue-liste-simulations.php] محاسبه کنیم:


<?php
// متغیرهای زیر ارث برده شده‌اند
// درخواست $request: درخواست فعلی
// جلسه $session: جلسه برنامه
// آرایه $config: پیکربندی برنامه
// آرایه $content: پاسخ کنترل‌کننده
//خطا ممکن نیست
// آرایه $content: پاسخ کنترلر
//
//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

//محاسبهٔ قالب نما
$modèle = getModelForThisView($request, $session, $config, $content);

function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
  // دربرگیری داده‌های صفحه در $modèle
  $modèle = new \stdClass();
  //شبیه‌سازی‌ها مطابق با نیاز صفحه قالب‌بندی می‌شوند
  //آن‌ها در پاسخ کنترلری که اقدام را اجرا کرده است یافت می‌شوند
  //به صورت آرایه‌ای از اشیاء از نوع [Simulation]
  $objetsSimulation = $content["réponse"];
  //هر شیء [Simulation] به یک آرایهٔ asociative تبدیل خواهد شد
  $modèle->simulations = [];
  foreach ($objetsSimulation as $objetSimulation) {
    $modèle->simulations[] = [
      "marié" => $objetSimulation->getMarié(),
      "enfants" => $objetSimulation->getEnfants(),
      "salaire" => $objetSimulation->getSalaire(),
      "impôt" => $objetSimulation->getImpôt(),
      "surcôte" => $objetSimulation->getSurcôte(),
      "décôte" => $objetSimulation->getdécôte(),
      "réduction" => $objetSimulation->getRéduction(),
      "taux" => $objetSimulation->getTaux()
    ];
  }
  // گزینه‌های منو
  $modèle->optionsMenu = [
    "Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
    "Fin de session" => "main.php?action=fin-session"];
  // قالب را رندر می‌کنیم
  return $modèle;
}
?>
<!--سند HTML -->
<!doctype html>
<html lang="fr">
    <head>
        
    </head>
    <body>
       
    </body>
</html>

نظرات

  • خطوط ۲۶–۳۶: محاسبه مدل [$modèle→simulations] مورد استفاده توسط قطعه [v-liste-simulations.php];
  • خطوط ۳۹–۴۱: محاسبه مدل [$modèle→optionsMenu] مورد استفاده توسط قطعه [v-menu.php]؛

23.13.4.4. تست‌های [Postman]

آزمون [lister-simulations-500] کد وضعیت ۵۰۰ را بازمی‌گرداند. این مربوط به درخواستی برای مشاهده شبیه‌سازی‌ها است:

Image

آزمون [supprimer-simulation-600] کد وضعیت 600 را برمی‌گرداند. این مربوط به حذف موفقیت‌آمیز شبیه‌سازی شماره 0 است. نتیجه بازگردانده‌شده فهرستی از شبیه‌سازی‌ها است که یک شبیه‌سازی در آن مفقود است:

Image

23.13.5. مشاهده خطاهای غیرمنتظره

در اینجا، «خطای غیرمنتظره» به خطایی گفته می‌شود که نباید در استفادهٔ عادی از برنامهٔ وب رخ می‌داد.

برای مثال، آزمون [Postman] [calculer-impot-3xx] را در نظر بگیرید که به شرح زیر تعریف شده است:

Image

  • در [1-3]، یک درخواست POST با اقدام [calculer-impot]؛
  • به [4-6]: در اینجا می‌توانید هر آنچه را که می‌خواهید برای سه پارامتر POST تعریف کنید:
    • [4]: پارامتر [marié] موجود نیست؛
    • [5-6]: پارامترهای [enfants, salaire] موجود هستند اما نامعتبر؛
  • در [9]، این سه خطا با کد وضعیت 338 گزارش شده‌اند؛

با این حال، در فرم HTML در برنامه وب، این وضعیت نمی‌تواند رخ دهد:

  • تمام پارامترها موجود هستند؛
  • پارامتر [marié] که مقدار خود را از ویژگی‌های [value] دو دکمه رادیویی می‌گیرد، لزوماً باید یکی از مقادیر [oui] یا [non] را داشته باشد؛
  • با یک مرورگر مدرن، ویژگی‌های <input type='number' min='0' step='1' …> تضمین می‌کنند که مقادیر واردشده برای children و salary لزوماً اعداد صحیح ≥۰ هستند؛

با این حال، هیچ چیزی مانع از این نمی‌شود که کاربر [Postman] را انتخاب کرده و تست [calcul-impot-3xx] که در بالا نشان داده شده را به سرور ما ارسال کند. ما مشاهده کرده‌ایم که اپلیکیشن وب ما توانسته است به درستی به این درخواست پاسخ دهد. ما به «خطای غیرمنتظره» به عنوان خطایی اشاره می‌کنیم که نباید در چارچوب برنامه HTML رخ دهد. اگر این خطا رخ دهد، احتمالاً کسی در تلاش برای «هک» برنامه است. به دلایل آموزشی، ما تصمیم گرفته‌ایم در چنین مواردی یک صفحه خطا نمایش دهیم. در واقع، می‌توانیم آخرین صفحه‌ای را که به کلاینت ارسال شده است مجدداً نمایش دهیم. برای این کار، کافی است آخرین پاسخ HTML را در جلسه (session) ذخیره کنیم. در صورت وقوع یک خطای غیرمنتظره، این پاسخ را بازمی‌گردانیم. به این ترتیب، کاربر تصور می‌کند که سرور به خطاهای او پاسخ نمی‌دهد، زیرا صفحه نمایش داده شده تغییر نمی‌کند.

23.13.5.1. نمای کلی صفحه

نمایانی که خطاهای غیرمنتظره را نمایش می‌دهد به شرح زیر است:

Image

نما تولید شده توسط اسکریپت [vue-erreurs.php] از سه بخش تشکیل شده است:

  • ۱: بنر بالایی توسط قطعه [v-bandeau.php] تولید می‌شود که قبلاً توضیح داده شده است؛
  • ۲: خطای(های) غیرمنتظره؛
  • ۳: منویی شامل سه لینک، تولیدشده توسط قطعه [v-menu.php]؛

نمایانی که خطاهای غیرمنتظره را نشان می‌دهد توسط اسکریپت زیر، [vue-erreurs.php تولید می‌شود:

Image


<?php
//قالب نما محاسبه می‌شود
$modèle = getModelForThisView();

function getModelForThisView(): object {
  // داده‌های صفحه را در $modèle دربر می‌گیرد
  $modèle = new \stdClass();

  //قالب بازگردانده می‌شود
  return $modèle;
}
?>
<!--سند HTML -->
<!doctype html>
<html lang="fr">
    <head>
        <!-- تگ‌های متای مورد نیاز -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!--بوت‌استرپ CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
        <title>Application impots</title>
    </head>
    <body>
        <div class="container">
            <!--بنر ۱۲ ستونی -->
            <?php require "v-bandeau.php"; ?>
            <!-- ردیف دو ستونی -->
            <div class="row">
                <!-- منوی سه‌ستونی-->
                <div class="col-md-3">
                    <?php require "v-menu.php" ?>
                </div>
                <!-- فهرست خطاها -->
                <div class="col-md-9">
                    <?php
                    print <<<EOT
                      <div class="alert alert-danger" role="alert">
                        Les erreurs inattendues suivantes se sont produites :
                        <ul>$modèle->erreurs</ul>
                      </div>
EOT;
                    ?>
                </div>
            </div>
        </div>
    </body>
</html>

نظرات

  • خط ۲۷: درج بنر برنامه [1]؛
  • خط ۳۲: درج منوی [2]. این منو در سه ستون زیر بنر نمایش داده خواهد شد؛
  • خطوط ۳۴–۴۴: نمایش ناحیه خطا در نه ستون؛
  • خطوط ۳۷–۴۴: عملیات [print] که خطاهای غیرمنتظره را نمایش می‌دهد؛
  • خط ۳۸: این نمایش در یک کادر Bootstrap با پس‌زمینه صورتی ظاهر خواهد شد؛
  • خط ۳۹: یک متن مقدماتی؛
  • خط ۴۰: تگ یک فهرست نقطه‌دار را در بر می‌گیرد. این فهرست نقطه‌دار توسط قالب [$modèle->erreurs] فراهم شده است؛

ما قبلاً دو قطعه از این نما را مورد بحث قرار داده‌ایم:

23.13.5.2. آزمون بصری

ما این عناصر مختلف را در پوشه [Tests] گردآوری کرده و یک قالب آزمایشی برای نما [vue-erreurs.php] ایجاد می‌کنیم:

Image

مدل داده برای نما [vue-erreurs.php] به شرح زیر خواهد بود:


<?php
// قالب نما محاسبه می‌شود
$modèle = getModelForThisView();

function getModelForThisView(): object {
  //داده‌های صفحه در $modèle جای‌گذاری شده‌اند
  $modèle = new \stdClass();

  //جدول خطاهای غیرمنتظره
  $erreurs = ["erreur1", "erreur2"];
  //فهرست خطاها تولید می‌شود HTML
  $modèle->erreurs = "";
  foreach ($erreurs as $erreur) {
    $modèle->erreurs .= "<li>$erreur</li>";
  }
  // گزینه‌های منو
  $modèle->optionsMenu = [
    "Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
    "Liste des simulations" => "main.php?action=lister-simulations",
    "Fin de session" => "main.php?action=fin-session",];
  //تصویر بنر
  $modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
  // بازگرداندن قالب
  return $modèle;
}
?>
<!-- HTML سند -->
<!doctype html>
<html lang="fr">
    <head>
        
    </head>
    <body>
        
    </body>
</html>

توضیحات

  • خطوط ۹–۱۵: ساخت لیست خطاهای HTML؛
  • خطوط 17–20: جدول گزینه‌های منو؛

بیایید این نما را نمایش دهیم:

Image

نتیجهٔ زیر به دست می‌آید:

Image

ما روی این نما کار می‌کنیم تا زمانی که از ظاهر آن راضی شویم. سپس می‌توانیم به ادغام این نما در وب‌اپلیکیشن در حال توسعه خود بپردازیم.

23.13.5.3. محاسبه مدل نما

Image

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


"vues": {
        "vue-authentification.php": [700, 221, 400],
        "vue-calcul-impot.php": [200, 300, 341, 350, 800],
        "vue-liste-simulations.php": [500, 600]
    },
"vue-erreurs": "vue-erreurs.php"

بنابراین، این کدهای وضعیت هستند که در خطوط [2-4] ذکر نشده‌اند و باعث نمایش «view» خطاهای غیرمنتظره می‌شوند.

کد محاسبه قالب نما [vue-erreurs.php] به شرح زیر است:


<?php
// متغیرهای زیر ارث برده شده‌اند
// درخواست $request: درخواست فعلی
// جلسه $session: جلسه برنامه
// آرایه $config: پیکربندی برنامه
// آرایه $content: پاسخ کنترلر
//
//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

// قالب نما محاسبه می‌شود
$modèle = getModelForThisView($request, $session, $config, $content);

function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
  // دربرگیری داده‌های صفحه در $modèle
  $modèle = new \stdClass();

  //بازیابی خطاها از پاسخ کنترلر
  $réponse = $content["réponse"];
  if (!is_array($réponse)) {
    //یک پیام خطای واحد
    $erreurs = [$réponse];
  } else {
    // چندین پیام خطا
    $erreurs = $réponse;
  }
  // فهرست خطاها ساخته می‌شود HTML
  $modèle->erreurs = "";
  foreach ($erreurs as $erreur) {
    $modèle->erreurs .= "<li>$erreur</li>";
  }
  // گزینه‌های منو
  $modèle->optionsMenu = [
    "Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
    "Liste des simulations" => "main.php?action=lister-simulations",
    "Fin de session" => "main.php?action=fin-session",];

  //قالب بازگردانده می‌شود
  return $modèle;
}
?>
<!--سند HTML -->
<!doctype html>
<html lang="fr">
    <head>
        
    </head>
    <body>
        
    </body>
</html>

نظرات

  • خطوط ۱۹–۳۲: محاسبه مدل [$modèle→erreurs] مورد استفاده در نمای [vue-erreurs.php
  • خطوط ۳۴–۳۷: محاسبه قالب [$modèle→optionsMenu] مورد استفاده توسط قطعه [v-menu.php]؛

23.13.5.4. آزمون‌های [Postman]

آزمون [calculer-impot-3xx] کد وضعیت ۳۳۸ را بازمی‌گرداند که کد وضعیت مورد انتظار نیست. بنابراین پاسخ HTML به شرح زیر است:

Image

23.13.6. پیاده‌سازی عملیات منوی برنامه

در اینجا پیاده‌سازی عملیات منو را بررسی خواهیم کرد. بیایید معنای لینک‌هایی را که با آن‌ها مواجه شده‌ایم به یاد بیاوریم

مشاهده
لینک
هدف
نقش
محاسبه مالیات
[Liste des simulations]
[main.php?action=lister-simulations]
درخواست فهرست شبیه‌سازی‌ها
 
[Fin de session]
[main.php?action=fin-session]
فهرست شبیه‌سازی‌ها
[Calcul de limpôt]
[main.php?action=afficher-calcul-impot]
نمایش نمای محاسبه مالیات
 
[Fin de session]
[main.php?action=fin-session]
خطاهای غیرمنتظره
[Calcul de limpôt]
[main.php?action=afficher-calcul-impot]
نمایش نمای محاسبه مالیات
 
[Liste des simulations]
[main.php?action=lister-simulations]
 
[Fin de session]
[main.php?action=fin-session]

شایان ذکر است که کلیک روی یک لینک، یک عمل GET را به مقصد لینک اجرا می‌کند. عملیات [lister-simulations, fin-session] با استفاده از عملیات GET پیاده‌سازی شده‌اند که به ما امکان می‌دهد آن‌ها را به‌عنوان مقصد لینک تنظیم کنیم. وقتی عمل از طریق POST اجرا می‌شود، دیگر نمی‌توان از لینک استفاده کرد مگر اینکه با جاوااسکریپت ترکیب شود.

از بین اقدامات فهرست‌شده در بالا، به نظر می‌رسد که اقدام [afficher-calcul-impot] هنوز پیاده‌سازی نشده است. این یک عملیات ناوبری بین دو نما است: سرورهای jSON یا XML دلیلی برای پیاده‌سازی آن ندارند، زیرا مفهوم «نما» را تشخیص نمی‌دهند. این سرور HTML است که این مفهوم را معرفی می‌کند.

بنابراین ما باید اقدام [afficher-calcul-impot] را پیاده‌سازی کنیم. این به ما امکان می‌دهد تا رویه پیاده‌سازی یک اقدام در داخل سرور را بازبینی کنیم.

ابتدا باید یک کنترلر ثانویه جدید اضافه کنیم. آن را [AfficherCalculImpotController] نام‌گذاری خواهیم کرد:

Image

این کنترلر باید به فایل پیکربندی [config.json] اضافه شود:


{
    "databaseFilename": "database.json",
    "rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-12",
    "relativeDependencies": [



        "/Controllers/InterfaceController.php",
        "/Controllers/InitSessionController.php",
        "/Controllers/ListerSimulationsController.php",
        "/Controllers/AuthentifierUtilisateurController.php",
        "/Controllers/CalculerImpotController.php",
        "/Controllers/SupprimerSimulationController.php",
        "/Controllers/FinSessionController.php",
        "/Controllers/AfficherCalculImpotController.php"
    ],
    "absoluteDependencies": [
        "C:/myprograms/laragon-lite/www/vendor/autoload.php",
        "C:/myprograms/laragon-lite/www/vendor/predis/predis/autoload.php"
    ],

    "actions":
            {
                "init-session": "\\InitSessionController",
                "authentifier-utilisateur": "\\AuthentifierUtilisateurController",
                "calculer-impot": "\\CalculerImpotController",
                "lister-simulations": "\\ListerSimulationsController",
                "supprimer-simulation": "\\SupprimerSimulationController",
                "fin-session": "\\FinSessionController",
                "afficher-calcul-impot": "\\AfficherCalculImpotController"
            },

    "vues": {
        "vue-authentification.php": [700, 221, 400],
        "vue-calcul-impot.php": [200, 300, 341, 350, 800],
        "vue-liste-simulations.php": [500, 600]
    },
    "vue-erreurs": "vue-erreurs.php"
}
  • خط ۱۵: کنترلر جدید؛
  • خط ۳۰: اکشن جدید و کنترلر آن؛
  • خط ۳۵: کنترلر جدید کد وضعیت ۸۰۰ را بازمی‌گرداند. هنگام تغییر ویوها نباید هیچ خطایی وجود داشته باشد؛

کنترل‌کننده [AfficherCalculImpotController.php] به شرح زیر خواهد بود:


<?php

namespace Application;

//وابستگی‌های Symfony
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Response;

class AfficherCalculImpotController implements InterfaceController {

  //$config پیکربندی برنامه است
  // پردازش یک درخواست
  //از Session استفاده می‌کند و می‌تواند آن را تغییر دهد
  // $infos جزئیات اضافی مختص هر کنترلر هستند
  //یک آرایه را بازمی‌گرداند [$statusCode, $état, $content, $headers]
  
  public function execute(
    array $config,
    Request $request,
    Session $session,
    array $infos = NULL): array {

    //تغییر نما – فقط یک کد وضعیت برای تنظیم
    return [Response::HTTP_OK, 800, ["réponse" => ""], []];
  }

}

توضیحات

  • خط ۱۰: مانند سایر کنترل‌کننده‌های ثانویه، کنترل‌کننده جدید رابط [InterfaceController] را پیاده‌سازی می‌کند؛
  • تغییرات نما ساده است: کافی است کد وضعیت مرتبط با نمای مقصد را تنظیم کنید، در این مورد کد 800 همان‌طور که در بالا مشاهده شد؛

23.13.7. آزمون زنده

کد نوشته شده و هر اقدام با استفاده از [Postman] آزمایش شده است. اکنون باید توالی ویوها را در یک سناریوی دنیای واقعی آزمایش کنیم. ما به روشی برای راه‌اندازی جلسه HTML نیاز داریم. ما می‌دانیم که باید پارامترهای [action=init-session&type=html] را به سرور ارسال کنیم. برای جلوگیری از تایپ کردن آن‌ها در نوار آدرس مرورگر، اسکریپت [index.php] را به برنامه‌مان اضافه خواهیم کرد:

Image

اسکریپت [index.php] به شرح زیر خواهد بود:


<?php

// ارسال مجدد به [main.php] در حالت [html]
header('Location: main.php?action=init-session&type=html');
  • خط ۴: [header] یک تابع PHP است که یک هدر HTTP به پاسخ اضافه می‌کند. سربرگ HTTP [Location: main.php?action=init-session&type=html] به مرورگر مشتری دستور می‌دهد تا به هدف مشخص‌شده در [Location] هدایت شود. اسکریپت [index.php] به همراه URL و [http://localhost/php7/scripts-web/impots/version-12/index.php] درخواست می‌شود. هنگامی که مرورگر کلاینت هدایت به URL نسبی را از [main.php?action=init-session&type=html] دریافت می‌کند، درخواست URL مطلق ([http://localhost/php7/scripts-web/impots/version-12/main.php?action=init-session&type=html]) را خواهد کرد و جلسه HTML آغاز خواهد شد؛

راه‌اندازی URL را می‌توان به [http://localhost/php7/scripts-web/impots/version-12/] ساده‌سازی کرد. اگر هیچ صفحه‌ای در URL مشخص نشود، صفحات [index.html, index.php] به‌صورت پیش‌فرض استفاده می‌شوند. در این صورت، اسکریپت [index.php] مورد استفاده قرار خواهد گرفت؛

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

در مرورگر خود، ردیابی درخواست را فعال می‌کنیم (F12 در فایرفاکس) و صفحهٔ شروع URL [https://localhost/php7/scripts-web/impots/version-12/] را درخواست می‌کنیم:

Image

  • در [4]، اولین پاسخ از سرور یک هدایت ۳۰۲ است:
  • به [5]، یک درخواست جدید به URL [http://localhost/php7/scripts-web/impots/13/main.php?action=init-session&type=html] ارسال می‌شود؛

بیایید نگاهی دقیق‌تر به هدایت ۳۰۲ بیندازیم:

Image

  • در [8]، کد HTTP [302] یک کد هدایت (redirect) است: به مرورگر کلاینت گفته می‌شود که URL درخواستی جابجا شده است. URL جدید به عنوان [9] مشخص شده است. مرورگر این هدایت را با یک درخواست جدید برای GET دنبال خواهد کرد:

Image

  • به [12-13]، درخواست جدید ارسال‌شده توسط مرورگر؛

بیایید فرم دریافتی را پر کنیم؛

Image

بیایید چند شبیه‌سازی انجام دهیم:

Image

Image

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

Image

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

Image

بیایید جلسه را پایان دهیم:

Image

از خوانندگان دعوت می‌شود تا آزمایش‌های بیشتری انجام دهند.

23.14. jSON کلاینت سرویس وب

23.14.1. معماری کلاینت/سرور

Image

اکنون به کلاینت jSON [A] سرویس وب [B] می‌پردازیم. کلاینت [A]، مانند سرویس وب [B]، دارای ساختار لایه‌ای است:

Image

این معماری در ساختار کد زیر منعکس شده است:

Image

اکثر کلاس‌ها قبلاً پوشش داده شده و توضیح داده شده‌اند:

BaseEntity
پاراگراف پیوند.
TaxPayerData
پاراگراف لینک.
Simulation
پاراگراف لینک.
ExceptionImpots
پاراگراف لینک.
TraitDao
پاراگراف لینک.
Utilitaires
پاراگراف لینک.

23.14.2. لایه [dao]

Image

23.14.2.1. رابط

رابط لایه [dao] به شرح زیر خواهد بود: [InterfaceClientDao.php]:


<?php

// فضای نام
namespace Application;

interface InterfaceClientDao {

  // خواندن داده‌های مودی
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // محاسبه مالیات مودی
  public function calculerImpot(string $marié, int $enfants, int $salaire): Simulation;

  //ثبت نتایج
  public function saveResults(string $resultsFilename, array $simulations): void;

  // احراز هویت
  public function authentifierUtilisateur(String $user, string $password): void;

  // فهرست شبیه‌سازی‌ها
  public function listerSimulations(): array;

  // حذف یک شبیه‌سازی
  public function supprimerSimulation(int $numéro): array;

  //ورود
  public function initSession(string $type = 'json'): void;

  //خروج
  public function finSession(): void;
}

نظرات

  • خط ۹: متد [getTaxPayersData] امکان پردازش فایل jSON حاوی داده‌های مودی مالیاتی را فراهم می‌کند. این متد توسط ویژگی [TraitDao] پیاده‌سازی شده است که قبلاً مورد بحث قرار گرفته است (به بخش «لینک» مراجعه کنید)؛
  • خط ۱۵: متد [saveResults] برای ذخیره نتایج چندین محاسبه مالیاتی در یک فایل jSON استفاده می‌شود. در اینجا نیز، این روش توسط ویژگی [TraitDao] پیاده‌سازی شده است که قبلاً مورد بحث قرار گرفته است (به بخش «لینک» مراجعه کنید
  • خطوط ۱۲، ۱۸، ۲۱، ۲۷، ۳۰: برای هر یک از اقداماتی که سرویس وب می‌پذیرد، یک متد ایجاد شده است؛

23.14.2.2. پیاده‌سازی

رابط [InterfaceClientDao] توسط کلاس زیر [ClientDao] پیاده‌سازی شده است:


<?php

namespace Application;

// وابستگی‌ها
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Response\CurlResponse;

class ClientDao implements InterfaceClientDao {
  // استفاده از یک ویژگی
  use TraitDao;
  // ویژگی‌ها
  private $urlServer;
  private $sessionCookie;
  private $verbose;

  // سازنده
  public function __construct(string $urlServer, bool $verbose = TRUE) {
    $this->urlServer = $urlServer;
    $this->verbose = $verbose;
  }

}

توضیحات

  • خطوط ۱۸–۲۱: سازنده دو پارامتر می‌گیرد:
    • پارامترهای URL و [$urlServer] از سرویس وب jSON؛
    • یک مقدار بولی [$verbose] که وقتی روی TRUE تنظیم شود، نشان می‌دهد که کلاس باید پاسخ‌های سرور را در کنسول نمایش دهد؛
  • خط ۱۴: کوکی جلسه. نقش آن در نسخه ۰۹ کلاینت توضیح داده شده است (به بخش «لینک» مراجعه کنید
  • خط ۱۱: کلاس از ویژگی [TraitDao] استفاده می‌کند که دو متد رابط را پیاده‌سازی می‌کند:
    • [getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array];
    • [function calculerImpot(string $marié, int $enfants, int $salaire): Simulation];

23.14.2.2.1. متد [initSession]

متد [initSession] به شرح زیر پیاده‌سازی شده است:


public function initSession(string $type = 'json'): void {
    // ایجاد یک کلاینت HTTP
    $httpClient = HttpClient::create();
    //ارسال درخواست به سرور بدون احراز هویت
    $response = $httpClient->request('GET', $this->urlServer,
      ["query" => [
          "action" => "init-session",
          "type" => $type
        ],
        "verify_peer" => false
    ]);
    //بازیابی پاسخ
    $this->getResponse($response);
    //بازیابی کوکی جلسه
    $headers = $response->getHeaders();
    if (isset($headers["set-cookie"])) {
      // کوکی جلسه؟
      foreach ($headers["set-cookie"] as $cookie) {
        $match = [];
        $match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
        if ($match) {
          $this->sessionCookie = "PHPSESSID=" . $champs[1];
        }
      }
    }
  }

از آنجایی که اقدام [init-session] باید اولین اقدام درخواست‌شده از سرویس وب باشد، متد [initSession] اولین متد در لایه [dao] خواهد بود که فراخوانی می‌شود.

توضیحات

  • خط ۱: نوع جلسهٔ مورد نظر به‌عنوان پارامتر ارسال می‌شود. اگر هیچ پارامتری ارائه نشود، یک جلسهٔ jSON آغاز خواهد شد؛
  • خطوط ۵–۱۱: یک درخواست GET به سرویس وب ارسال می‌شود؛
  • خطوط ۷–۸: دو پارامتر برای GET؛
  • خط ۱۰: در صورت ارتباطات امن (HTTPS)، گواهی امنیتی ارسال‌شده توسط سرویس وب تأیید نخواهد شد؛
  • خط ۱۳: متد [getResponse] پاسخ سرور را بازیابی می‌کند. آن را به صورت یک آرایه بازمی‌گرداند. در اینجا، نتیجه متد استفاده نمی‌شود. متد [getResponse] در صورتی که کد HTTP در پاسخ سرویس وب، 200 OK نباشد، یک استثنا (exception) پرتاب می‌کند؛
  • خطوط 14–25: از آنجایی که متد [initSession] اولین متد در لایه [dao] است که اجرا می‌شود، کوکی جلسه بازیابی می‌شود تا متدهای بعدی بتوانند آن را به سرویس وب بازگردانند. این کد در نسخه 09 قبلاً غیرفعال شده است؛

23.14.2.2.2. متد [getResponse]

متد [getResponse] مسئول پردازش پاسخ سرویس وب است:


private function getResponse(CurlResponse $response) {
    //پاسخ دریافت می‌شود
    $json = $response->getContent(false);
    // لاگ‌ها
    if ($this->verbose) {
      print "$json\n";
    }
    // بازیابی وضعیت پاسخ
    $statusCode = $response->getStatusCode();
    // خطا؟
    if ($statusCode !== 200) {
      // یک خطا رخ داده است
      throw new ExceptionImpots($json);
    }
    // ما پاسخ خود را بازمی‌گردانیم
    $array = json_decode($json, true);
    return $array["réponse"];
  }

توضیحات

  • خط ۱: متد خصوصی است؛
  • خط ۱: پارامتر متد پاسخ سرویس وب از نوع [Symfony\Component\HttpClient\Response\CurlResponse]، نوع پاسخ Symfony، زمانی که [HttpClient] توسط [CurlClient] پیاده‌سازی شده باشد، است، یعنی توسط کتابخانه [curl]؛
  • خط ۳: پاسخ jSON از سرور بازیابی می‌شود. توجه داشته باشید که پارامتر [false] برای جلوگیری از به وجود آمدن خطا (exception) توسط Symfony در زمانی که وضعیت پاسخ HTTP سرور در بازه [3xx, 4xx, 5xx] قرار می‌گیرد، وجود دارد؛
  • خطوط ۵–۷: اگر در حالت [$verbose] باشد، پاسخ سرور روی کنسول نمایش داده می‌شود؛
  • خطوط ۹–۱۴: اگر وضعیت پاسخ سرور HTTP برابر با ۲۰۰ نباشد، یک استثنا با پاسخ سرور jSON به عنوان پیام خطا پرتاب می‌شود؛
  • خط 16: رشته jSON به یک آرایه تبدیل می‌شود؛
  • خط ۱۷: اطلاعات مربوطه در [$array["réponse"]] قرار دارد؛

23.14.2.2.3. روش [authentifierUtilisateur]

روش [authentifierUtilisateur] به شرح زیر است:


public function authentifierUtilisateur(string $user, string $password): void {
    // ایجاد یک کلاینت HTTP
    $httpClient = HttpClient::create();
    //ارسال درخواست به سرور با احراز هویت
    $response = $httpClient->request('POST', $this->urlServer,
      ["query" => [
          "action" => "authentifier-utilisateur"
        ],
        "body" => [
          "user" => $user,
          "password" => $password
        ],
        "verify_peer" => false,
        "headers" => ["Cookie" => $this->sessionCookie]
    ]);
    //بازیابی پاسخ
    $this->getResponse($response);
  }

نظرات

  • خط ۵: درخواست کلاینت یک POST است؛
  • خطوط ۶–۸: پارامترها در URL;
  • خطوط ۹–۱۲: پارامترها در POST;
  • خط ۱۴: کوکی جلسه؛
  • خط ۱۷: پاسخ خوانده می‌شود. می‌دانیم که در صورت بروز خطا (کد HTTP غیر از ۲۰۰)، خود متد [getResponse] یک استثنا پرتاب می‌کند؛

23.14.2.2.4. متد [calculerImpot]

public function calculerImpot(string $marié, int $enfants, int $salaire): Simulation {
    //یک کلاینت ایجاد می‌شود: HTTP
    $httpClient = HttpClient::create();
    // یک درخواست بدون احراز هویت اما با کوکی جلسه به سرور ارسال می‌شود
    $response = $httpClient->request('POST', $this->urlServer,
      ["query" => [
          "action" => "calculer-impot"],
        "body" => [
          "marié" => $marié,
          "enfants" => $enfants,
          "salaire" => $salaire
        ],
        "verify_peer" => false,
        "headers" => ["Cookie" => $this->sessionCookie]
    ]);
    //پاسخ دریافت می‌شود
    $array = $this->getResponse($response);
    return (new Simulation())->setFromArrayOfAttributes($array);
  }

توضیحات

  • خطوط ۶–۷: تنها پارامتر URL؛
  • خطوط ۸–۱۲: سه پارامتر POST (خط ۵);
  • خط ۱۷: پاسخ پردازش می‌شود؛
  • خط ۱۸: اگر به این نقطه برسیم، یعنی متد [getResponse] خطایی (exception) ایجاد نکرده است. ما یک شیء [Simulation] را که با آرایه بازگردانده شده توسط [getResponse] مقداردهی اولیه شده است، برمی‌گردانیم؛

23.14.2.2.5. متد [listerSimulations]

public function listerSimulations(): array {
    //ایجاد یک کلاینت: HTTP
    $httpClient = HttpClient::create();
    //یک درخواست بدون احراز هویت اما با کوکی جلسه به سرور ارسال می‌شود
    $response = $httpClient->request('GET', $this->urlServer,
      ["query" => [
          "action" => "lister-simulations"
        ],
        "verify_peer" => false,
        "headers" => ["Cookie" => $this->sessionCookie]
    ]);
    //پاسخ بازیابی می‌شود
    return $this->getSimulations($response);
  }

توضیحات

  • خط ۵: متد GET;
  • خطوط ۶–۸: تنها پارامتر GET;
  • خط ۱۳: بازیابی شبیه‌سازی‌ها توسط متد خصوصی [getSimulations] انجام می‌شود؛

23.14.2.2.6. متد [getSimulations]

private function getSimulations(CurlResponse $response): array {
    // بازیابی پاسخ JSON
    $array = $this->getResponse($response);
    //ما یک آرایهٔ asociative داریم
    // ما این را به آرایه‌ای از اشیاء شبیه‌سازی تبدیل خواهیم کرد
    $simulations = [];
    foreach ($array as $simulation) {
      $simulations [] = (new Simulation())->setFromArrayOfAttributes($simulation);
    }
    // ما لیست اشیاء شبیه‌سازی را بازمی‌گردانیم
    return $simulations;
}

نظرات

  • خط ۳: آرایه از پاسخ بازیابی می‌شود. این یک آرایه از آرایه‌ها است که هر کدام از آن‌ها دارای تمام ویژگی‌های یک شی [Simulation] هستند؛
  • خط ۶: اگر به این نقطه برسیم، یعنی متد [getResponse] خطایی را ایجاد نکرده است؛
  • خطوط ۶–۹: ما از پاسخ برای ساخت آرایه‌ای از اشیاء [Simulation] استفاده می‌کنیم؛
  • خط ۱۱: این آرایه بازگردانده می‌شود؛

23.14.2.2.7. متد [SupprimerSimulation]

public function supprimerSimulation(int $numéro): array {
    // ما یک کلاینت ایجاد می‌کنیم HTTP
    $httpClient = HttpClient::create();
    // ما درخواست را بدون احراز هویت اما با کوکی جلسه به سرور ارسال می‌کنیم
    $response = $httpClient->request('GET', $this->urlServer,
      ["query" => [
          "action" => "supprimer-simulation",
          "numéro" => $numéro
        ],
        "verify_peer" => false,
        "headers" => ["Cookie" => $this->sessionCookie]
    ]);
    // پاسخ را بازیابی کنید
    return $this->getSimulations($response);
  }

توضیحات

  • خط ۵: یک پرس‌وجو با استفاده از GET انجام می‌شود؛
  • خطوط ۶–۹: دو پارامتر برای URL;
  • خط 14: پس از یک حذف، سرور آرایه جدید شبیه‌سازی‌ها را بازمی‌گرداند. این آرایه بازگردانده می‌شود؛

23.14.2.2.8. متد [finSession]

یک جلسه کاری با سرویس وب معمولاً با فراخوانی متد [finSession] به پایان می‌رسد:


public function finSession(): void {
    //ایجاد یک کلاینت: HTTP
    $httpClient = HttpClient::create();
    //یک درخواست بدون احراز هویت اما با کوکی جلسه به سرور ارسال می‌شود
    $response = $httpClient->request('GET', $this->urlServer,
      ["query" => [
          "action" => "fin-session"
        ],
        "verify_peer" => false,
        "headers" => ["Cookie" => $this->sessionCookie]
    ]);
    //پاسخ دریافت می‌شود
    $this->getResponse($response);
  }

نظرات

  • خط ۵: یک درخواست GET ارسال می‌شود؛
  • خطوط ۶–۸: تنها پارامتر URL;
  • خط ۱۳: پاسخ خوانده می‌شود. اگر کد HTTP در پاسخ برابر با ۲۰۰ نباشد، یک استثنا پرتاب خواهد شد؛

23.14.3. لایه [métier]

Image

23.14.3.1. رابط

رابط لایه [métier] به شرح زیر است: [InterfaceClientMetier.php]:


<?php

//فضای نام
namespace Application;

interface InterfaceClientMetier {

  //محاسبه مالیات‌های یک مؤدی
  public function calculerImpot(string $marié, int $enfants, int $salaire): Simulation;

  //محاسبه مالیات‌ها در حالت دسته‌ای
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFilename, string $errorsFileName): void;

  // احراز هویت
  public function authentifierUtilisateur(String $user, string $password): void;

  // فهرست شبیه‌سازی‌ها
  public function listerSimulations(): array;

  // ذخیره نتایج
  public function saveResults(string $resultsFilename, array $simulations): void;

  // حذف یک شبیه‌سازی
  public function supprimerSimulation(int $numéro): array;

  // ورود
  public function initSession(string $type = 'json'): void;

  //پایان جلسه
  public function finSession(): void;
}

نظرات

  • فقط متد [executeBatchImpots] در خط ۱۲ مختص لایه [métier] است. سایر متدها به لایه [dao] تعلق دارند که آن‌ها را پیاده‌سازی می‌کند؛

23.14.3.2. کلاس [ClientMetier]

کلاسی که لایه [métier] را پیاده‌سازی می‌کند به شرح زیر است:


<?php

namespace Application;

class ClientMetier implements InterfaceClientMetier {
  // ویژگی
  private $clientDao;

  // تولیدکننده
  public function __construct(InterfaceClientDao $clientDao) {
    $this->clientDao = $clientDao;
  }

  // محاسبه مالیات
  public function calculerImpot(string $marié, int $enfants, int $salaire): Simulation {
    return $this->clientDao->calculerImpot($marié, $enfants, $salaire);
  }

  // محاسبه مالیات دسته‌ای
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // اجازه می‌دهد استثناءهای ناشی از لایه [dao] منتقل شوند
    //بازیابی داده‌های مودی
    $taxPayersData = $this->clientDao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // جدول نتایج
    $simulations = [];
    // پردازش نتایج
    foreach ($taxPayersData as $taxPayerData) {
      // مالیات محاسبه می‌شود     
      $simulations [] = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
    }
    //نتایج ثبت می‌شوند
    if ($resultsFileName !== NULL) {
      $this->clientDao->saveResults($resultsFileName, $simulations);
    }
  }

  public function authentifierUtilisateur(String $user, string $password): void {
    $this->clientDao->authentifierUtilisateur($user, $password);
  }

  public function listerSimulations(): array {
    return $this->clientDao->listerSimulations();
  }

  public function saveResults(string $resultsFilename, array $simulations): void {
    $this->clientDao->saveResults($resultsFilename, $simulations);
  }

  public function supprimerSimulation(int $numéro): array {
    return $this->clientDao->supprimerSimulation($numéro);
  }

  public function finSession(): void {
    $this->clientDao->finSession();
  }

  public function initSession(string $type = 'json'): void {
    $this->clientDao->initSession($type);
  }

}

توضیحات

  • خطوط ۱۰–۱۲: برای ساخت، لایه [métier] به یک مرجع به لایه [dao] نیاز دارد؛
  • خطوط ۲۰–۳۸: تنها متد [executeBatchImpots] مختص لایه [métier] است. پیاده‌سازی متدهای دیگر، کار را به متدهایی با همان نام در لایه [dao] واگذار می‌کند؛
  • خط ۲۳: لایه [dao] فراخوانی می‌شود تا داده‌های مالیات‌دهنده را در آرایه‌ای از اشیاء از نوع [TaxPayerData] بازیابی کند؛
  • خط ۲۵: شبیه‌سازی‌های محاسبه‌شده مختلف در آرایه [$simulations] تجمیع می‌شوند؛
  • خطوط ۲۷–۳۳: مالیات هر مؤدی در آرایه [$taxPayersData] محاسبه می‌شود؛
  • خطوط ۳۵–۳۷: نتایج حاصل در جدول [$simulations] در فایلی به نام jSON ذخیره می‌شوند؛

توجه: لایه [métier] عملاً هیچ کاری انجام نمی‌دهد. ممکن است تصمیم گرفته شود که آن را حذف کرده و همه چیز را در لایه [dao] ادغام کنند.

23.14.4. اسکریپت اصلی

Image

اسکریپت اصلی توسط فایل زیر پیکربندی می‌شود: [config.json]:


{
    "taxPayersDataFileName": "Data/taxpayersdata.json",
    "resultsFileName": "Data/results.json",
    "errorsFileName": "Data/errors.json",
    "rootDirectory": "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-12",
    "dependencies": [
        "/Entities/BaseEntity.php",
        "/Entities/TaxPayerData.php",
        "/Entities/Simulation.php",
        "/Entities/ExceptionImpots.php",
        "/Utilities/Utilitaires.php",
        "/Model/InterfaceClientDao.php",        
        "/Model/TraitDao.php",
        "/Model/ClientDao.php",
        "/Model/InterfaceClientMetier.php",
        "/Model/ClientMetier.php"
    ],
    "absoluteDependencies": [
        "C:/myprograms/laragon-lite/www/vendor/autoload.php"
    ],
    "user": {
        "login": "admin",
        "passwd": "admin"
    },
    "urlServer": "https://localhost:443/php7/scripts-web/impots/version-12/main.php"
}

اسکریپت اصلی [main.php] به شرح زیر است:


<?php

// پابندی سخت‌گیرانه به انواع اعلام‌شدهٔ پارامترهای تابع
declare(strict_types = 1);

// فضای نام
namespace Application;

// مدیریت خطا توسط PHP
// ini_set("display_errors", "0");
//
// مسیر فایل پیکربندی
define("CONFIG_FILENAME", "../Data/config.json");

//بازیابی پیکربندی
$config = \json_decode(file_get_contents(CONFIG_FILENAME), true);

//وابستگی‌های لازم اسکریپت گنجانده شده‌اند
$rootDirectory = $config["rootDirectory"];
foreach ($config["dependencies"] as $dependency) {
  require "$rootDirectory/$dependency";
}
//وابستگی‌های مطلق (کتابخانه‌های شخص ثالث)
foreach ($config["absoluteDependencies"] as $dependency) {
  require "$dependency";
}

// تعریف ثابت‌ها
define("TAXPAYERSDATA_FILENAME", "$rootDirectory/{$config["taxPayersDataFileName"]}");
define("RESULTS_FILENAME", "$rootDirectory/{$config["resultsFileName"]}");
define("ERRORS_FILENAME", "$rootDirectory/{$config["errorsFileName"]}");
//
//وابستگی‌های Symfony
use Symfony\Component\HttpClient\HttpClient;

// ایجاد لایه [dao]
$clientDao = new ClientDao($config["urlServer"]);
// ایجاد لایه [métier]
$clientMetier = new ClientMetier($clientDao);

//محاسبه دسته‌ای مالیات
try {
  // ابتدای جلسه
  $clientMetier->initSession('json');
  //احراز هویت
  $clientMetier->authentifierUtilisateur($config["user"]["login"], $config["user"]["passwd"]);
  //محاسبه مالیات بدون ذخیره نتایج
  $clientMetier->executeBatchImpots(TAXPAYERSDATA_FILENAME, NULL, ERRORS_FILENAME);
  // فهرست شبیه‌سازی‌ها
  $clientMetier->listerSimulations();
  // حذف یک شبیه‌سازی
  $simulations = $clientMetier->supprimerSimulation(1);
  //ذخیره نتایج
  $clientMetier->saveResults(RESULTS_FILENAME, $simulations);
  // پایان جلسه
  $clientMetier->finSession();
  // اقدام بدون ورود به سیستم – باید باعث کرش شود
  $clientMetier->listerSimulations();
} catch (ExceptionImpots $ex) {
  // خطا نمایش داده می‌شود
  print "Une erreur s'est produite : " . $ex->getMessage() . "\n";
}
//پایان
print "Terminé\n";
exit();

توضیحات

  • خطوط ۱۲–۱۶: پردازش فایل پیکربندی [config.json];
  • خطوط ۱۸–۲۶: بارگذاری تمام وابستگی‌ها؛
  • خطوط ۲۸–۳۴: تعریف ثابت‌ها و نام‌های مستعار؛
  • خطوط ۳۶–۳۹: ساخت لایه‌های [dao] و [métier]؛
  • خط ۴۴: راه‌اندازی یک جلسه jSON؛
  • خط ۴۶: احراز هویت با سرور؛
  • خط ۴۸: محاسبه مالیات برای مجموعه‌ای از مودیان. نتایج ذخیره نمی‌شوند (پارامتر دوم NULL);
  • خط ۵۰: درخواست نتایج تمام این محاسبات؛
  • خط ۵۲: شبیه‌سازی شماره ۱ (دومین مورد در لیست) حذف می‌شود؛
  • خط ۵۴: شبیه‌سازی‌های باقی‌مانده ذخیره می‌شوند؛
  • خط ۵۶: جلسه پایان می‌یابد. این بدان معناست که کوکی جلسه حذف می‌شود؛
  • خط ۵۸: لیست شبیه‌سازی‌ها درخواست می‌شود. از آنجایی که کوکی جلسه حذف شده است، احراز هویت باید دوباره انجام شود. بنابراین باید یک خطا رخ دهد که بیان می‌کند کاربر احراز هویت نشده است؛

فایل [taxpayersdata.json] به شرح زیر است:


[
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 55555
    },
    {
        "marié": "ouix",
        "enfants": "2x",
        "salaire": "55555x"
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": 50000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 50000
    },
    {
        "marié": "non",
        "enfants": 2,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 5,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 30000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 200000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 20000
    }
]

۱۲ مالیات‌دهنده وجود دارد که یکی از آنها نادرست است. این در مجموع ۱۱ شبیه‌سازی می‌شود. یکی از آنها حذف خواهد شد. باید ۱۰ مورد باقی بماند.

پس از اجرای اسکریپت اصلی، فایل jSON [results.json] به شرح زیر است:


[
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": "55555",
        "impôt": 2814,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.14
    },
    {
        "marié": "oui",
        "enfants": "3",
        "salaire": "50000",
        "impôt": 0,
        "surcôte": 0,
        "décôte": 720,
        "réduction": 0,
        "taux": 0.14
    },
    {
        "marié": "non",
        "enfants": "2",
        "salaire": "100000",
        "impôt": 19884,
        "surcôte": 4480,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.41
    },
    {
        "marié": "non",
        "enfants": "3",
        "salaire": "100000",
        "impôt": 16782,
        "surcôte": 7176,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.41
    },
    {
        "marié": "oui",
        "enfants": "3",
        "salaire": "100000",
        "impôt": 9200,
        "surcôte": 2180,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.3
    },
    {
        "marié": "oui",
        "enfants": "5",
        "salaire": "100000",
        "impôt": 4230,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.14
    },
    {
        "marié": "non",
        "enfants": "0",
        "salaire": "100000",
        "impôt": 22986,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.41
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": "30000",
        "impôt": 0,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0
    },
    {
        "marié": "non",
        "enfants": "0",
        "salaire": "200000",
        "impôt": 64210,
        "surcôte": 7498,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.45
    },
    {
        "marié": "oui",
        "enfants": "3",
        "salaire": "20000",
        "impôt": 0,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0
    }
]

در واقع ۱۰ شبیه‌سازی وجود دارد.

فایل jSON [errors.json] دارای محتوای زیر است:


{
    "numéro": 1,
    "erreurs": [
        {
            "marié": "ouix"
        },
        {
            "enfants": "2x"
        },
        {
            "salaire": "55555x"
        }
    ]
}

خروجی کنسول به شرح زیر است (در حالت verbose، پاسخ‌های سرور به jSON در کنسول نمایش داده می‌شوند):


{"action":"init-session","état":700,"réponse":"session démarrée avec type [json]"}
{"action":"authentifier-utilisateur","état":200,"réponse":"Authentification réussie [admin, admin]"}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"2","salaire":"55555","impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"2","salaire":"50000","impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"3","salaire":"50000","impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"2","salaire":"100000","impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"3","salaire":"100000","impôt":16782,"surcôte":7176,"décôte":0,"réduction":0,"taux":0.41}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"3","salaire":"100000","impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"5","salaire":"100000","impôt":4230,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"0","salaire":"100000","impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"2","salaire":"30000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"0","salaire":"200000","impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"3","salaire":"20000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}}
{"action":"lister-simulations","état":500,"réponse":[{"marié":"oui","enfants":"2","salaire":"55555","impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"oui","enfants":"2","salaire":"50000","impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"50000","impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"2","salaire":"100000","impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"non","enfants":"3","salaire":"100000","impôt":16782,"surcôte":7176,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"100000","impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3,"arrayOfAttributes":null},{"marié":"oui","enfants":"5","salaire":"100000","impôt":4230,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"100000","impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"2","salaire":"30000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"200000","impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"20000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null}]}
{"action":"supprimer-simulation","état":600,"réponse":[{"marié":"oui","enfants":"2","salaire":"55555","impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"50000","impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"2","salaire":"100000","impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"non","enfants":"3","salaire":"100000","impôt":16782,"surcôte":7176,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"100000","impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3,"arrayOfAttributes":null},{"marié":"oui","enfants":"5","salaire":"100000","impôt":4230,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"100000","impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"2","salaire":"30000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"200000","impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"20000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null}]}
{"action":"fin-session","état":400,"réponse":"session supprimée"}
{"action":"lister-simulations","état":103,"réponse":["pas de session en cours. Commencer par action [init-session]"]}
Une erreur s'est produite : {"action":"lister-simulations","état":103,"réponse":["pas de session en cours. Commencer par action [init-session]"]}
Terminé

23.14.5. آزمایش‌های [Codeception]

مانند نسخه‌های قبلی، مشتری نسخه ۱۲ ممکن است مشمول تست‌های [Codeception] باشد:

Image

کد کلاس تست برای لایه [métier] مشتری مشابه کد کلاس‌های تست برای مشتریان قبلی است:


<?php

// پابندی سخت‌گیرانه به انواع اعلام‌شدهٔ پارامترهای تابع
declare (strict_types=1);

// فضای نام
namespace Application;

// تعریف‌های ثابت
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-12");
// مسیر فایل پیکربندی
define("CONFIG_FILENAME", ROOT . "/Data/config.json");

// بازیابی پیکربندی
$config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);

// شامل وابستگی‌های مورد نیاز اسکریپت
$rootDirectory = $config["rootDirectory"];
foreach ($config["dependencies"] as $dependency) {
  require "$rootDirectory$dependency";
}
//وابستگی‌های مطلق (کتابخانه‌های شخص ثالث)
foreach ($config["absoluteDependencies"] as $dependency) {
  require "$dependency";
}
//وابستگی‌های Symfony
use Symfony\Component\HttpClient\HttpClient;

//کلاس تست
class ClientDaoTest extends \Codeception\Test\Unit {
  // لایه DAO
  private $clientDao;

  public function __construct() {
    parent::__construct();
    // بازیابی پیکربندی
    $config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
    // ایجاد لایه [dao]
    $clientDao = new ClientDao($config["urlServer"]);
    //ایجاد لایه [métier]
    $this->métier = new ClientMetier($clientDao);
    // ابتدای جلسه
    $this->métier->initSession("json");
    //احراز هویت
    $this->métier->authentifierUtilisateur("admin", "admin");
  }

  // آزمایش‌ها
  public function test1() {
    $simulation = $this->métier->calculerImpot("oui", 2, 55555);
    $this->assertEqualsWithDelta(2815, $simulation->getImpôt(), 1);
    $this->assertEqualsWithDelta(0, $simulation->getSurcôte(), 1);
    $this->assertEqualsWithDelta(0, $simulation->getDécôte(), 1);
    $this->assertEqualsWithDelta(0, $simulation->getRéduction(), 1);
    $this->assertEquals(0.14, $simulation->getTaux());
  }

  public function test2() {
    ….
  }


  public function test11() {

  }

}

توضیحات

  • خطوط ۳۴–۴۶: توجه داشته باشید که سازنده کلاس تست قبل از هر تست اجرا می‌شود؛
  • خطوط ۳۸–۴۱: ساخت لایه‌های [dao] و [métier]؛
  • خطوط ۴۲–۴۵: متدهای تست [test1…, test11] متد [calculerImpot] را تست می‌کنند. برای اینکه این کار ممکن شود، ابتدا یک جلسه jSON باید راه‌اندازی شود و کاربر باید احراز هویت کند؛

نتایج آزمون به شرح زیر است:

Image

تست‌های بسیار بیشتری باید انجام شود:

  • روش‌های مختلف لایه [dao] را آزمایش کنید؛
  • آزمون وضعیت‌های بازگردانده‌شده توسط سرور وب. این وضعیت‌ها مهم هستند زیرا مقدار آن‌ها تعیین می‌کند که کدام صفحه HTML نمایش داده شود؛