10. التطبيقات ذات الطبقات
10.1. مقدمة

سنستكشف الآن كيفية هيكلة تطبيق PHP في طبقات:

في هذا الرسم التوضيحي، تبدأ الطبقة اليسرى في استخدام الطبقة اليمنى. وتتمثل أدوار الطبقات فيما يلي:
- [1]: تتولى الطبقة المسماة [DAO] (كائنات الوصول إلى البيانات) التعامل مع مخازن البيانات الخارجية [4] (الملفات وقواعد البيانات وخدمات الويب، وما إلى ذلك). تُسمى هذه الطبقة أحيانًا [DAL] (طبقة الوصول إلى البيانات)، وهو مصطلح يصف دور الطبقة بشكل أفضل. يمكن لهذه الطبقة قراءة البيانات [3] أو كتابة البيانات [2]. يتم استدعاؤها من قبل طبقة [الأعمال] [6] وتعيد النتائج إليها [7]؛
- [5]: طبقة تسمى [business] تحتوي على إجراءات "الأعمال"، والتي يتم تزويدها بجميع البيانات التي تحتاجها. هذه هي عمومًا الطبقة الأكثر استقرارًا في المشروع لأنها لا تعتمد على كيفية الحصول على البيانات. تأتي البيانات من مصدرين:
- [9]: البيانات التي يوفرها البرنامج النصي PHP؛
- [6،7]: البيانات المطلوبة من طبقة [DAO].
- [8]: يعمل البرنامج النصي الرئيسي كمنسق. في تطبيق وحدة التحكم، سيقوم بما يلي:
- إنشاء طبقتي [business] و [DAO]؛
- تنفيذ خوارزمية التطبيق. تعمل هذه الخوارزمية كمنسق: فهي لا تحتوي على منطق "الأعمال" أو كود الوصول إلى البيانات. يقوم البرنامج النصي الرئيسي ببساطة باستدعاء إجراءات طبقة [الأعمال] [9]. يتجاهل تمامًا طبقة [DAO] والبيانات الخارجية. يمكنه توفير البيانات لطبقة [الأعمال] [9]. في تطبيق وحدة التحكم، قد تأتي هذه البيانات من ملفات التكوين أو من مستخدم البرنامج النصي. يتلقى النتائج [10] من طبقة [الأعمال]. قد يحتاج إلى تخزين نتائج معينة: للقيام بذلك، يستخدم مرة أخرى إجراءات طبقة [الأعمال]، والتي بدورها ستتواصل مع طبقة [DAO] لأداء المهمة؛
- من بين النتائج، قد يتلقى البرنامج النصي الرئيسي استثناءات. وتتمثل مهمته في معالجة الاستثناءات التي تنتشر من جميع الطبقات؛
تم تصميم هذه البنية الطبقية لتسهيل تطوير التطبيق. ولهذا الغرض، تنفذ كل طبقة واجهة. لنفترض أن:
- يتم تنفيذ طبقة [DAO] بواسطة فئة [DAO] التي تنفذ واجهة [IDao]؛
- يتم تنفيذ طبقة [business] بواسطة فئة [Business] التي تنفذ واجهة [IBusiness]؛
ستستخدم الإجراءات في طبقة [business] واجهة [IDao] بدلاً من فئة [Dao]. وهذا يسمح بتحديث طبقة [Dao] دون التأثير على طبقة [Business]. لنفترض أنه في الإصدار 1، تستخدم طبقة [Dao1] بيانات من قاعدة بيانات. بعد التحديث، يتم توفير هذه البيانات الآن بواسطة خدمة ويب في الإصدار 2، [Dao2]. سنضمن أن كلا من فئتي [Dao1] و[Dao2] تنفذان نفس واجهة [IDao] وترميان نفس الاستثناءات. إذا تم التحقق من ذلك، فستبقى طبقة [Business] التي تعمل مع واجهة [IDao] دون تغيير؛
وينطبق نفس المنطق على طبقة [Business].
دعونا نلقي نظرة على تطبيق باستخدام الفئات والواجهات:

سيطبق التطبيق الهيكل الطبقي التالي:

10.2. الكائنات المتبادلة بين الطبقات
عادةً، تتبادل الطبقات كائنات مختلفة. هنا، ستتبادل الطبقات الفئة التالية [Person.php]:
<?php
// a person
class Personne {
// identifier
private $id;
// manufacturer
public function __construct(int $id) {
$this->id = $id;
}
// toString
public function __toString(): string {
return "[Personne($this->id)]";
}
}
تعليقات
- السطر 6: الشخص له سمة واحدة فقط، وهي معرّفه [$id]. سنفترض أن هذا يحدد شخصًا فريدًا في مستودع البيانات؛
- الأسطر 9–11: المنشئ الذي يسمح لنا بإنشاء كائن [Person] باستخدام معرّفه؛
- الأسطر 14–16: طريقة [__toString] التي تعرض معرف الشخص؛
10.3. طبقة [DAO]
واجهة [IDao] التي تنفذها طبقة [dao, 1] هي كما يلي [IDao.php]:
<?php
// layer [dao] ------------------------
interface IDao {
// recovery of a person from an external warehouse
// pass the person's login
public function get(int $id): Personne;
// saving a person in an external warehouse
public function save(Personne $p): void;
}
يتم تنفيذ هذه الواجهة بواسطة الفئة [Dao1] التالية [Dao1.php]:
<?php
class Dao1 implements IDao {
// saving a person in an external warehouse
public function save(Personne $p): void {
print "[Dao1] : Sauvegarde de la personne $p en base de données [locale]\n";
}
// recovery of a person from an external warehouse
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);
}
}
تعليقات
- لا تتفاعل الفئة مع مستودع البيانات. نحن نعرض ببساطة رسائل لتتبع تنفيذ الكود (السطران 7 و 12)؛
- السطر 13: نُرجع شخصًا بمعرف تم تمريره كمعلمة إلى الأسلوب (السطر 11)؛
نقوم أيضًا بتنفيذ واجهة [IDao] باستخدام الفئة [Dao2] التالية [Dao2.php]:
<?php
class Dao2 implements IDao {
// saving a person in an external warehouse
public function save(Personne $p): void {
print "[Dao2] : Sauvegarde de la personne $p en base de données [distante]\n";
}
// recovery of a person from an external warehouse
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. طبقة [Business]
توفر طبقة [business, 2] واجهة [IMetier] التالية [IMetier.php]:
<?php
// business] layer ------------------------
interface IMétier extends IDao {
// we use a person identified by his id
public function doSomething(Personne $p): void;
}
تعليقات
- توسع واجهة [IMétier] واجهة [IDao]. وهذا ليس إلزامياً على الإطلاق. نحن نفعل ذلك هنا لأن المثال بسيط؛
- السطر 7: طريقة [doSomething] خاصة بطبقة [Business]؛
سيتم تنفيذ واجهة [IMetier] بواسطة فئة [Métier] التالية [Métier.php]:
<?php
class Métier implements IMétier {
// layer [dao]
private $dao;
// getter / setter
public function setDao(IDao $dao): void {
$this->dao = $dao;
}
public function getDao(): IDao {
return $this->dao;
}
// a person is exploited
public function doSomething(Personne $p): void {
// treatment
print "Métier : Traitement métier de la personne $p\n";
}
// safeguarding the individual
public function save(Personne $p): void {
print "[Métier] : Sauvegarde de la personne $p\n";
// the [dao] layer is asked to make the backup
$this->dao->save($p);
}
public function get(int $id): Personne {
print "[Métier] : récupération de la personne d'identifiant $id\n";
// we ask for the person in the diaper [dao]
$personne = $this->dao->get($id);
return $personne;
}
}
تعليقات
- السطر 5: يجب أن تحتوي طبقة [business] على مرجع إلى طبقة [DAO] من أجل استخدام أساليبها؛
- الأسطر 8–10: تسمح طريقة [setDao] بتزويد طبقة [business] بإشارة إلى طبقة [DAO]. لاحظ أن نوع المعلمة هو [IDao]. وهذا يعني أن طبقة [business] ستكون قادرة على العمل مع أي فئة تنفذ واجهة [IDao]. إذا قمنا بالتبديل من طبقة [Dao1] إلى طبقة [Dao2] وكلاهما ينفذ واجهة [IDao]، فلن تكون هناك حاجة لإعادة كتابة طبقة [business]؛
- الأسطر 17-20: تنفيذ [IMetier::doSomething]؛
- الأسطر 23–27: تنفيذ [IMetier::save]. يجب أن تحفظ هذه الطريقة شخصًا في مستودع البيانات. لا تستطيع طبقة [business] القيام بذلك. فهي تستدعي طبقة [DAO] لتنفيذ عملية الحفظ هذه؛
- الأسطر 29–34: تنفيذ [IMetier::get]. يجب أن تسترد هذه الطريقة من مستودع البيانات الشخص الذي تم تمرير معرفه إليها كمعلمة. لا تعرف طبقة [business] كيفية القيام بذلك. فهي تستدعي طبقة [DAO] للقيام بهذه المهمة (السطر 32)؛
الخلاصة
عندما تحتاج طبقة [الأعمال] إلى الوصول إلى البيانات المخزنة في مستودع البيانات، يجب أن تمر عبر طبقة [DAO]، التي تم إنشاؤها للوصول إلى هذه البيانات.
10.5. النص البرمجي الرئيسي
سنكتب نصين برمجيين ليقوما بدور المنسقين لهذا التطبيق. سيستخدم النص الأول [main1.php] طبقة [Dao1]، بينما سيستخدم النص الثاني [main2.php] طبقة [Dao2]. نريد أن نثبت أن هذا لا يؤثر على الكود في طبقة [الأعمال].
النص البرمجي [main1.php] هو كما يلي:
<?php
// strict adherence to function parameters
declare (strict_types=1);
// inclusion classes and interfaces
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";
// test ----------------
// layer creation
$dao1 = new Dao1();
$métier = new Métier();
$métier->setDao($dao1);
// using the [business] layer
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);
تعليقات
- دعونا نستعرض بعض النقاط: يعمل البرنامج النصي [main1.php] كمنسق للتطبيق. فهو ينشئ البنية الطبقية للتطبيق (الأسطر 15–17) ثم يبدأ في التواصل مع الطبقة [business] (الأسطر 19–21). البنية الطبقية هي كما يلي:

وفقًا لهذا الرسم التخطيطي، يجب أن يتفاعل البرنامج النصي [main1.php] مع الطبقة [business] فقط. ولا يجب أن يتفاعل مع الطبقة [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]
تعليقات
- تسبب السطر 10 من الكود في كتابة السطرين 1 و 2 من النتائج؛
- تسبب السطر 20 من الكود في كتابة السطر 3 من النتائج؛
- تسبب السطر 21 من الكود في كتابة السطرين 4 و 5 من النتائج؛
النص البرمجي [main2.php] هو كما يلي:
<?php
// strict adherence to function parameters
declare (strict_types=1);
// inclusion classes and interfaces
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";
// test ----------------
// layer creation
$dao2 = new Dao2();
$métier = new Métier();
$métier->setDao($dao2);
// using the [business] layer
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);
تعليقات
- الأسطر 36–38: تستخدم البنية الطبقية الآن طبقة [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]، يمكننا أن نرى أن الكود في طبقة [Business] لم يحتج إلى تغيير.
سنطبق ما تعلمناه للتو على تمرين حساب الضرائب الذي يمثل خيطنا التوجيهي.