Skip to content

10. التطبيقات ذات الطبقات

10.1. مقدمة

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] والبيانات الخارجية. ويمكنه تزويد الطبقة [métier] ببيانات من الطبقة [9]. في تطبيق وحدة التحكم، قد تأتي هذه البيانات من ملفات التكوين أو من مستخدم البرنامج النصي. ويتلقى النتائج [10] من الطبقة [métier]. قد يحتاج إلى تخزين بعض النتائج: ولهذا الغرض، يستخدم مرة أخرى إجراءات الطبقة [métier] التي ستتواصل بدورها مع الطبقة [dao] التي ستقوم بالمهمة؛
    • من بين النتائج، قد يتلقى البرنامج النصي الرئيسي استثناءات. وتتمثل مهمته في إدارة الاستثناءات التي يتم رفعها من جميع الطبقات؛

يهدف هذا التقسيم إلى طبقات إلى تسهيل تطوير التطبيق. ولهذا الغرض، تقوم كل طبقة بتنفيذ واجهة. لنفترض أن:

  • يتم تنفيذ الطبقة [dao] بواسطة فئة [Dao] التي تنفذ واجهة [IDao]؛
  • يتم تنفيذ الطبقة [métier] بواسطة فئة [Métier] التي تنفذ واجهة [IMétier]؛

ستستخدم إجراءات الطبقة [métier] واجهة [IDao] بدلاً من الفئة [Dao]. وهذا يسمح بتطوير الطبقة [Dao] دون المساس بالطبقة [Métier]. لنفترض أنه في الإصدار 1، تستخدم الطبقة [Dao1] بيانات من قاعدة بيانات. وبعد إجراء تحديث، أصبحت هذه البيانات تُقدَّم الآن بواسطة خدمة ويب في الإصدار 2، [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)]";
  }

}

تعليقات

  • السطر 6: لا يمتلك الشخص سوى سمة واحدة، وهي معرّفه [$id]. سنفترض أن هذا المعرّف يشير إلى شخص فريد في مستودع البيانات؛
  • الأسطر 9-11: المنشئ الذي يسمح بإنشاء نوع [Personne] باستخدام معرّفه؛
  • الأسطر 14-16: الطريقة [__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);
  }

}

تعليقات

  • لا تتفاعل الفئة مع مستودع البيانات. نكتفي بعرض رسائل لمتابعة سير العمل في الكود (السطران 7 و 12)؛
  • السطر 13: يتم بالفعل إرجاع شخص يحمل المعرّف الذي تم تمريره كمعلمة إلى الدالة (السطر 11)؛

كما نقوم بتنفيذ الواجهة [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]. هذا ليس إلزاميًا على الإطلاق. نقوم بذلك هنا لأن المثال بسيط؛
  • السطر 7: الطريقة [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;
  }

}

تعليقات

  • السطر 5: يجب أن يكون للطبقة [métier] مرجع إلى الطبقة [dao] حتى تتمكن من استخدام أساليب هذه الأخيرة؛
  • الأسطر 8-10: تسمح الطريقة [setDao] بإعطاء الطبقة [métier] مرجع الطبقة [dao]. تجدر الإشارة إلى أن نوع المعلمة هو [IDao]. وبالتالي، ستتمكن الطبقة [métier] من العمل مع أي فئة تنفذ الواجهة [IDao]. إذا انتقلنا من طبقة [Dao1] إلى طبقة [Dao2]، وكلاهما ينفذان الواجهة [IDao]، فلن يكون هناك داعٍ لإعادة كتابة الطبقة [métier]؛
  • الأسطر 17-20: تنفيذ [IMetier::doSomething
  • الأسطر 23-27: تنفيذ [IMetier::save]. يجب أن تقوم هذه الطريقة بحفظ شخص ما في مستودع البيانات. الطبقة [métier] لا تستطيع القيام بذلك. فهي تلجأ إلى الطبقة [dao] لإجراء عملية الحفظ هذه؛
  • الأسطر 29-34: تنفيذ [IMetier::get]. يجب أن تسترد هذه الطريقة من مستودع البيانات الشخص الذي تم تمرير معرّفه كمعلمة إليها. الطبقة [métier] غير قادرة على القيام بذلك. وهي تلجأ إلى الطبقة [dao] للقيام بهذه المهمة (السطر 32)؛

الخلاصة

بمجرد أن تحتاج الطبقة [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] هو «قائد الأوركسترا» للتطبيق. فهو ينشئ بنية الطبقات للتطبيق (الأسطر 15-17) ثم يبدأ بعد ذلك في التفاعل مع الطبقة [métier] (الأسطر 19-21). والهيكل الطبقي هو في الواقع كما يلي:

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]

تعليقات

  • تسبب السطر 10 من الكود في كتابة السطرين 1 و2 من النتائج؛
  • تسبب السطر 20 من الكود في كتابة السطر 3 من النتائج؛
  • تسبب السطر 21 من الكود في كتابة السطرين 4 و5 من النتائج؛

النص البرمجي [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);

تعليقات

  • الأسطر 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]، نرى أن كود الطبقة [Métier] لم يتطلب أي تغيير.

سنطبق ما تعلمناه للتو على تمرين حساب الضرائب الذي نستخدمه كخيط رفيع.