Skip to content

10. برنامه‌های لایه‌بندی‌شده

10.1. Introduction

Image

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

Image

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

  • [1]: لایه‌ای که با نام [dao] (ابجکت‌های دسترسی به داده) شناخته می‌شود، تعاملات با مخازن داده خارجی [4] (فایل‌ها، پایگاه‌های داده، سرویس‌های وب و غیره) را مدیریت می‌کند. گاهی به این لایه [DAL] (لایه دسترسی به داده) گفته می‌شود، اصطلاحی که نقش آن را بهتر توصیف می‌کند. این لایه می‌تواند داده‌ها را از [3] بخواند یا داده‌ها را به [2] بنویسد. این لایه توسط لایه‌های [métier] و [6] فراخوانی می‌شود و نتایج را در قالب [7] به آن‌ها بازمی‌گرداند؛
  • [5]: لایه‌ای به نام [métier] که رویه‌های «کسب‌وکار» را گرد هم می‌آورد و با تمام داده‌های مورد نیازشان تأمین می‌شود. این معمولاً پایدارترین لایه در یک پروژه است زیرا به نحوهٔ به‌دست‌آوردن داده‌ها بستگی ندارد. داده‌ها دو منبع دارند:
    • [9]: داده‌های ارائه‌شده توسط اسکریپت PHP;
    • [6,7]: داده‌های درخواست‌شده از لایه [dao].
  • [8]: اسکریپت اصلی به‌عنوان هماهنگ‌کننده عمل می‌کند. در یک برنامه کنسولی، آن انجام خواهد داد:
    • لایه‌های [métier] و [dao] را ایجاد می‌کند؛
    • الگوریتم برنامه را اجرا می‌کند. این الگوریتم به‌عنوان یک «راهنما» عمل می‌کند: هیچ «منطق کسب‌وکار» یا کُد دسترسی به داده‌ای در آن وجود ندارد. اسکریپت اصلی صرفاً رویه‌های موجود در لایه‌های [métier] و [9] را فراخوانی می‌کند. این لایه به طور کامل لایه [dao] و داده‌های خارجی را نادیده می‌گیرد. این لایه می‌تواند داده‌ها را از [9] به لایه [métier] ارائه دهد. در یک برنامه کنسول، این داده‌ها ممکن است از فایل‌های پیکربندی یا از اجرای اسکریپت توسط کاربر بیایند. این لایه نتایج [10] را از لایه [métier] دریافت می‌کند. ممکن است نیاز باشد که نتایج خاصی را ذخیره کند: برای این کار، دوباره از رویه‌های لایه [métier] استفاده می‌کند که به نوبه خود، برای انجام این وظیفه، به لایه [dao] فراخوانی می‌کند؛
    • در میان نتایج، اسکریپت اصلی ممکن است استثناءها را دریافت کند. وظیفه آن رسیدگی به استثناءهایی است که از تمام لایه‌ها به سمت بالا منتقل می‌شوند؛

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

  • لایه [dao] توسط کلاسی به نام [Dao] پیاده‌سازی شده است که رابط [IDao] را پیاده‌سازی می‌کند؛
  • لایه [métier] توسط کلاسی به نام [Métier] پیاده‌سازی شده است که رابط [IMétier] را پیاده‌سازی می‌کند؛

روندهای لایه [métier] سپس از رابط [IDao] استفاده خواهند کرد به جای کلاس [Dao]. این امکان را فراهم می‌کند که لایه [Dao] بدون تأثیر بر لایه [Métier] به‌روزرسانی شود. فرض کنید در نسخهٔ ۱، لایهٔ [Dao1] از داده‌های یک پایگاه داده استفاده می‌کند. پس از یک به‌روزرسانی، این داده‌ها اکنون توسط یک سرویس وب در نسخهٔ ۲، [Dao2]، ارائه می‌شوند. ما اطمینان حاصل می‌کنیم که هر دو کلاس، [Dao1] و [Dao2 همان رابط، [IDao را پیاده‌سازی می‌کنند و همان استثناءها را پرتاب می‌کنند. اگر این موضوع تأیید شود، لایه [Métier] که با رابط [IDao] کار می‌کند، بدون تغییر باقی خواهد ماند؛

همان استدلال در مورد لایه [Métier] نیز صدق می‌کند.

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

Image

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

Image

10.2. ابجکت‌های مبادله شده بین لایه‌ها

معمولاً لایه‌ها اشیاء مختلفی را مبادله می‌کنند. در اینجا، آن‌ها کلاس زیر را مبادله خواهند کرد، [Personne.php]:


<?php

//یک شخص
class Personne {
  //شناسه
  private $id;

  // سازنده
  public function __construct(int $id) {
    $this->id = $id;
  }

  // toString
  public function __toString(): string {
    return "[Personne($this->id)]";
  }

}

نظرات

  • خط ۶: شخص تنها یک ویژگی دارد، شناسهٔ او [$id]. فرض می‌کنیم که این شناسه، شخص منحصربه‌فردی را در یک انبار داده مشخص می‌کند؛
  • خطوط ۹–۱۱: سازنده (constructor) مورد استفاده برای ایجاد یک نوع [Personne] با شناسه‌ی آن؛
  • خطوط ۱۴–۱۶: متد [__toString] که شناسه‌ی شخص را نمایش می‌دهد؛

10.3. لایه [dao]

رابط [IDao] که توسط لایه [dao, 1] پیاده‌سازی شده است به شرح زیر است: [IDao.php]:


<?php


//لایه [dao] ------------------------
interface IDao {

  // جمع‌آوری یک شخص از انبار خارجی
  // شخصیت فرد مشخص شده است
  public function get(int $id): Personne;

  //ذخیره یک شخص در مخزن خارجی
  public function save(Personne $p): void;
}

این رابط توسط کلاس زیر [Dao1] پیاده‌سازی شده است: [Dao1.php]:


<?php

class Dao1 implements IDao {

  //ذخیره یک شخص در مخزن خارجی
  public function save(Personne $p): void {
    print "[Dao1] : Sauvegarde de la personne $p en base de données [locale]\n";
  }

  // نجات یک نفر از انبار خارجی
  public function get(int $id): Personne {
    print "[Dao1] : Récupération de la personne d'identité ($id) en base de données [locale]\n";
    return new Personne($id);
  }

}

نظرات

  • این کلاس با انبار داده تعامل ندارد. این کلاس صرفاً پیام‌هایی را برای ردیابی اجرای کد نمایش می‌دهد (خطوط ۷ و ۱۲)؛
  • خط ۱۳: به درستی یک شخص را با شناسه ارسال شده به عنوان پارامتر به متد (خط ۱۱) بازمی‌گرداند؛

ما همچنین رابط [IDao] را با کلاس‌های زیر [Dao2] و [Dao2.php] پیاده‌سازی می‌کنیم:


<?php

class Dao2 implements IDao {

  //ذخیره یک شخص در مخزن خارجی
  public function save(Personne $p): void {
    print "[Dao2] : Sauvegarde de la personne $p en base de données [distante]\n";
  }

  // نجات یک نفر از یک انبار خارجی
  public function get(int $id): Personne {
    print "[Dao2] : Récupération de la personne d'identité ($id) en base de données [distante]\n";
    return new Personne($id);
  }

}

کلاس [Dao2] مشابه کلاس [Dao1] است، با این تفاوت که ما پیام‌های نمایش‌داده‌شده را تغییر داده‌ایم.

10.4. لایه [métier]

لایه [métier, 2] رابط [IMétier] زیر را فراهم می‌کند: [IMetier.php]:


<?php

//لایه [métier] ------------------------
interface IMétier extends IDao {

  // شخصی که با کارت شناسایی‌اش شناسایی شده در حال پردازش است
  public function doSomething(Personne $p): void;
}

نظرات

  • رابط [IMétier] رابط [IDao] را گسترش می‌دهد. این به هیچ وجه اجباری نیست. ما این کار را در اینجا انجام می‌دهیم زیرا مثال ساده است؛
  • خط ۷: متد [doSomething] مختص لایه [Métier] است؛

رابط [IMétier] توسط کلاس زیر [Métier]، [Métier.php] پیاده‌سازی خواهد شد:


<?php

class Métier implements IMétier {
  //لایه [dao]
  private $dao;

  // گیرنده / تنظیم‌کننده
  public function setDao(IDao $dao): void {
    $this->dao = $dao;
  }

  public function getDao(): IDao {
    return $this->dao;
  }

  // یک شخص مورد بهره‌کشی قرار می‌گیرد
  public function doSomething(Personne $p): void {
    //پردازش
    print "Métier : Traitement métier de la personne $p\n";
  }

  // نجات فرد
  public function save(Personne $p): void {
    print "[Métier] : Sauvegarde de la personne $p\n";
    // به لایه [dao] دستور داده می‌شود تا عملیات ذخیره را انجام دهد
    $this->dao->save($p);
  }

  public function get(int $id): Personne {
    print "[Métier] : récupération de la personne d'identifiant $id\n";
    // فرد از لایه [dao] درخواست می‌شود
    $personne = $this->dao->get($id);
    return $personne;
  }

}

نظرات

  • خط ۵: لایه [métier] باید به لایه [dao] ارجاع دهد تا بتواند از متدهای آن استفاده کند؛
  • خطوط ۸–۱۰: متد [setDao] اجازه می‌دهد مرجع لایه [dao] به لایه [métier] اختصاص یابد. توجه داشته باشید که نوع پارامتر [IDao] است. بنابراین، لایه [métier] قادر خواهد بود با هر کلاسی که رابط [IDao] را پیاده‌سازی می‌کند، کار کند. اگر از لایه [Dao1] به لایه [Dao2] سوئیچ کنیم و هر دو رابط [IDao] را پیاده‌سازی کنند، لایه [métier] نیازی به بازنویسی ندارد؛
  • خطوط ۱۷–۲۰: پیاده‌سازی [IMetier::doSomething];
  • خطوط ۲۳–۲۷: پیاده‌سازی [IMetier::save]. این روش باید یک شخص را در انبار داده ذخیره کند. لایه [métier] قادر به انجام این کار نیست. این لایه از لایه [dao] برای انجام این ذخیرهسازی فراخوانی می‌کند؛
  • خطوط ۲۹–۳۴: پیاده‌سازی [IMetier::get]. این روش باید شخص را از انبار داده بازیابی کند که شناسهٔ او به‌عنوان پارامتر به آن ارسال می‌شود. لایه [métier] قادر به انجام این کار نیست. این لایه از لایه [dao] برای انجام این وظیفه فراخوانی می‌کند (خط ۳۲)؛

نتیجه‌گیری

هرگاه لایه [métier] نیاز به دسترسی به داده‌های ذخیره‌شده در انبار داده داشته باشد، باید از طریق لایه [dao] که برای دسترسی به این داده‌ها ایجاد شده است، اقدام کند.

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

ما قصد داریم دو اسکریپت بنویسیم تا به عنوان «راهنما» برای این برنامه عمل کنند. اسکریپت اول، [main1.php از لایه [Dao1] استفاده خواهد کرد، در حالی که اسکریپت دوم، [main2.php از لایه [Dao2] استفاده خواهد کرد. ما می‌خواهیم نشان دهیم که این هیچ تأثیری بر کد در لایه [Métier] ندارد.

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


<?php

// رعایت دقیق پارامترهای تابع
declare (strict_types=1);

// شامل کردن کلاس‌ها و رابط‌ها
require_once __DIR__."/Personne.php";
require_once __DIR__."/IDao.php";
require_once __DIR__."/IMetier.php";
require_once __DIR__."/Dao1.php";
require_once __DIR__."/Métier.php";

// آزمایش ----------------
// ایجاد لایه‌ها
$dao1 = new Dao1();
$métier = new Métier();
$métier->setDao($dao1);
// استفاده از لایه [métier]
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);

نظرات

  • بیایید چند نکته را مرور کنیم: اسکریپت [main1.php] به‌عنوان هماهنگ‌کنندهٔ برنامه عمل می‌کند. این اسکریپت ساختار لایه‌ای برنامه را ایجاد می‌کند (خطوط ۱۵–۱۷) و سپس تعامل با لایهٔ [métier] را آغاز می‌کند (خطوط ۱۹–۲۱). ساختار لایه‌ای به شرح زیر است:

Image

طبق این نمودار، اسکریپت [main1.php] باید فقط با لایه [métier] ارتباط برقرار کند. این اسکریپت نباید با لایه [dao] ارتباط برقرار کند، حتی اگر از نظر تئوری این کار ممکن باشد.

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


[Métier] : récupération de la personne d'identifiant 4
[Dao1] : Récupération de la personne d'identité (4) en base de données [locale]
[Métier] : Traitement métier de la personne [Personne(4)]
[Métier] : Sauvegarde de la personne [Personne(4)]
[Dao1] : Sauvegarde de la personne [Personne(4)] en base de données [locale]

توضیحات

  • خط ۱۰ کد باعث شد خطوط ۱ و ۲ نتایج نوشته شوند؛
  • خط ۲۰ کد باعث نوشتن خط ۳ نتایج شد؛
  • خط ۲۱ کد باعث شد خطوط ۴ و ۵ نتایج نوشته شوند؛

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


<?php

// رعایت دقیق پارامترهای تابع
declare (strict_types=1);

// شامل کردن کلاس‌ها و رابط‌ها
require_once __DIR__."/Personne.php";
require_once __DIR__."/IDao.php";
require_once __DIR__."/IMetier.php";
require_once __DIR__."/Dao2.php";
require_once __DIR__."/Métier.php";

// آزمایش ----------------
// ایجاد لایه‌ها
$dao2 = new Dao2();
$métier = new Métier();
$métier->setDao($dao2);
// استفاده از لایه [métier]
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);

نظرات

  • خطوط ۳۶–۳۸: ساختار لایه اکنون از لایه [Dao2] استفاده می‌کند؛

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


[Métier] : récupération de la personne d'identifiant 4
[Dao2] : Récupération de la personne d'identité (4) en base de données [distante]
[Métier] : Traitement métier de la personne [Personne(4)]
[Métier] : Sauvegarde de la personne [Personne(4)]
[Dao2] : Sauvegarde de la personne [Personne(4)] en base de données [distante]

لایه [Dao1] دسترسی به یک پایگاه داده محلی را شبیه‌سازی می‌کند، در حالی که لایه [Dao2] دسترسی به یک پایگاه داده از راه دور را شبیه‌سازی می‌کند. تا زمانی که این دو لایه با رابط [IDao] مطابقت داشته باشند، می‌توانیم ببینیم که کد لایه [Métier] نیازی به تغییر نداشته است.

ما قصد داریم آنچه را که همین حالا آموخته‌ایم در تمرین محاسبه مالیات به کار ببریم که به‌عنوان نخ راهنمای ما عمل می‌کند.