Skip to content

5. الفئات

المصطلحات: الفئة (class) هي نوع PHP. والمتغير من هذا النوع يُسمى كائنًا. والكائن هو مثيل (نسخة) من الفئة.

5.1. هيكل الشجرة للنصوص البرمجية

Image

5.2. يمكن لأي متغير أن يصبح كائنًا مزودًا بسمات

النص البرمجي [classes-01.php] هو التالي:


<?php

// كائن عام
// $obj1=new stdClass();
// يمكن أن يكون لأي متغير سمات حسب البنية
$obj1->attr1 = "un";
$obj1->attr2 = 100;
// يعرض الكائن
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// يُعدّل الكائن
$obj1->attr2 += 100;
// يعرض الكائن
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// ينسخ قيمة الكائن 1 (عنوان الكائن المشار إليه) إلى الكائن 2
// تصبح المتغيرتان مختلفتين عندئذٍ لكنهما تشيران إلى نفس الكائن
$obj2 = $obj1;
// يعدل الكائن 2
$obj2->attr2 = 0;
// يعرض الكائنين
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
// يغير الكائن الذي يشير إليه obj1
$obj1 = new stdClass();
print "obj1 :\n";
print_r($obj1);
print "obj2 :\n";
print_r($obj2);
// يعين مرجع (عنوان) الكائن 2 إلى الكائن 3
// يصبح كل من $obj2 و $obj3 متغيرًا واحدًا
$obj3 = &$obj2;
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);
// يُعدِّل الكائن obj3
$obj3->attr2 = 10;
// يعرض الكائنين
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
print "objet3=[$obj3->attr1,$obj3->attr2]\n";
// يغير الكائن الذي يشير إليه obj2
$obj2 = new stdClass();
$obj2->attr3 = "deux";
$obj2->attr4 = 20;
// يعرض الكائنين $obj2 و $obj3
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);

// هل الكائن عبارة عن قاموس؟
print count($obj3) . "\n";
while (list($attribut, $valeur) = each($obj3)) {
  print "obj3[$attribut]=$valeur\n";
}
// نهاية
exit;

النتائج:


Warning: Creating default object from empty value in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_14.php on line 6
objet1=[un,100]
objet1=[un,200]
objet1=[un,0]
objet2=[un,0]
obj1 :
stdClass Object
(
)
obj2 :
stdClass Object
(
    [attr1] => un
    [attr2] => 0
)
obj2 :
stdClass Object
(
    [attr1] => un
    [attr2] => 0
)
obj3 :
stdClass Object
(
    [attr1] => un
    [attr2] => 0
)
objet2=[un,10]
objet3=[un,10]
obj2 :
stdClass Object
(
    [attr3] => deux
    [attr4] => 20
)
obj3 :
stdClass Object
(
    [attr3] => deux
    [attr4] => 20
)

Warning: count(): Parameter must be an array or an object that implements Countable in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_14.php on line 50
1

Deprecated: The each() function is deprecated. This message will be suppressed on further calls in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_14.php on line 51
obj3[attr3]=deux
obj3[attr4]=20

تعليقات

  • السطر 6: تشير الصيغة $obj->attr إلى السمة attr للمتغير $obj. وإذا لم يكن موجودًا، يتم إنشاؤه، مما يجعل المتغير $obj كائنًا له سمات. وقد رأينا أن PHP ينشئ افتراضيًا كائنًا من النوع stdClass؛
  • السطر 16: التعبير $obj2=$obj1، عندما يكون $obj1 كائنًا، يمثل نسخًا للكائنات بالرجوع: $obj2 و $obj1 هما مرجعان (عنوانان) لكائن واحد. ويمكن تعديل الكائن نفسه بواسطة أي من هذين المرجعين؛
  • السطور 23-27: تهدف إلى إظهار أن $obj1 و $obj2 متغيران مختلفان: فهما لا يقعان في نفس عنوان الذاكرة:
    • $obj2=$obj1 نسخ قيمة $obj1 إلى المتغير $obj2 (العملية 1 أعلاه). قيمة $obj1 هي عنوان أحد الكائنات. وبالتالي، فإن $obj1 و$obj2 يشيران إلى نفس الكائن. عند التعامل مع المتغير $obj الذي يشير إلى كائن ما، فإن المتغير PHP يتعامل مع الكائن الذي يشير إليه المتغير $obj. وفقًا للرسم التخطيطي أدناه، نرى أنه يمكن تعديل الكائن المشار إليه إما عبر $obj1 أو عبر $obj2. وهذا ما تظهره السطران 4 و5 من النتائج؛

Image

  • السطر 30: التعبير $obj3=&$obj2 يجعل $obj2 و $obj3 في نفس العنوان [1 ci-dessous]. يمكن القول إن المتغيرين هما اسمان بديلان لنفس موقع الذاكرة. كلاهما يشير إلى كائن واحد، وهو «الكائن أ» الموضح أدناه [2]؛
    • تؤدي العملية $obj2=new stdClass() إلى إنشاء كائن جديد، «الكائن ب» ([3 ci-dessous])، ويتم تعيين عنوان هذا الكائن الجديد للمتغير $obj2. ونظرًا لأن $obj2 و $obj3 هما اسمان مستعاران لنفس موقع الذاكرة، فإن $obj3 يشير أيضًا إلى الكائن الجديد «الكائن ب». وهذا ما توضحه الأسطر 16-27 و30-41 من النتائج؛

Image

  • الأسطر 52-54: توضح أنه يمكن تصفح الكائن كقاموس. مفاتيح القاموس هي أسماء السمات وقيم القاموس، وهي قيم هذه السمات نفسها؛
  • السطر 51: يمكن تطبيق الدالة count على كائن (مع ظهور تحذير)، لكنها لا تعطي، كما كان متوقعًا، عدد السمات. لذا، فإن الكائن يشبه القاموس ولكنه ليس قاموسًا؛

5.3. فئة «Personne» بدون سمات مُعلنة

النص البرمجي [classes-02.php] هو التالي:


<?php

class Personne {

  // سمات الفئة
  // غير مُعلنة - يمكن إنشاؤها ديناميكيًا
  // طريقة
  function identite() {
    // في البداية، تستخدم سمات غير موجودة
    return "[$this->prenom,$this->nom,$this->age]";
  }

}

// اختبار
// السمات عامة ويمكن إنشاؤها ديناميكيًا
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// استدعاء طريقة
print "personne=" . $p->identite() . "\n";
// نهاية
exit;

النتائج:

personne=[Paul,Langevin,48]

تعليقات

  • الأسطر 3-13: تحدد فئة Personne. الفئة هي قالب يُستخدم لإنشاء كائنات. وهي تضم سمات ووظائف تُسمى طرقًا. ليس من الضروري إعلان السمات؛
  • الأسطر 8-11: تعرض الطريقة identité قيمة ثلاث سمات غير مُعلنة في الفئة. تشير الكلمة الرئيسية $this إلى الكائن الذي تُطبق عليه الطريقة؛
  • السطر 17: يتم إنشاء كائن $p من النوع Personne. تُستخدم الكلمة الرئيسية new لإنشاء كائن جديد. تُرجع العملية مرجعًا إلى الكائن الذي تم إنشاؤه (أي عنوانًا). يمكن كتابتها بعدة طرق: new Personne()، new Personne، new personne. اسم الفئة غير حساس لحالة الأحرف؛
  • السطور 18-20: يتم إنشاء السمات الثلاث اللازمة للطريقة identité في الكائن $p؛
  • السطر 22: يتم تطبيق الأسلوب identité الخاص بالفئة Personne على الكائن $p. في كود (الأسطر 8-11) للطريقة identité، تشير $this إلى نفس الكائن الذي تشير إليه $p؛

5.4. فئة Personne مع السمات المعلنة

النص البرمجي [classes-03.php] هو كما يلي:


<?php

class Personne {

  // سمات الفئة
  var $prenom;
  var $nom;
  var $age;

  // طريقة
  function identite() {
    return "[$this->prenom,$this->nom,$this->age]";
  }

}

// اختبار
// السمات عامة
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// استدعاء طريقة
print "personne=" . $p->identite() . "\n";
// نهاية
exit;

النتائج:

personne=[Paul,Langevin,48]

تعليقات

  • الأسطر 6-8: يتم إعلان سمات الفئة صراحةً باستخدام الكلمة الرئيسية var؛

5.5. فئة Personne مع منشئ

أظهرت الأمثلة السابقة فئات Personne غير تقليدية كما يمكن العثور عليها في PHP 4. لا يُنصح باتباع هذه الأمثلة. نقدم الآن فئة Personne [classes-04.php] تتوافق مع الممارسات الجيدة في PHP 7:


<?php

// الالتزام الصارم بأنواع المعلمات المعلنة للدوال
declare (strict_types=1);
class Personne {
// سمات الفئة
  private $prenom;
  private $nom;
  private $age;

// دالات القراءة والكتابة
  public function getPrenom(): string {
    return $this->prenom;
  }

  public function getNom(): string {
    return $this->nom;
  }

  public function getAge(): int {
    return $this->age;
  }

  public function setPrenom(string $prenom): void {
    $this->prenom = $prenom;
  }

  public function setNom(string $nom): void {
    $this->nom = $nom;
  }

  public function setAge(int $age): void {
    $this->age = $age;
  }

// منشئ
  public function __construct(string $prenom, string $nom, int $age) {
    // نستخدم وظائف التعيين
    $this->setPrenom($prenom);
    $this->setNom($nom);
    $this->setAge($age);
  }

// طريقة toString
  public function __toString(): string {
    return "[$this->prenom,$this->nom,$this->age]";
  }

}

// اختبار
// إنشاء كائن «شخص»
$p = new Personne("Paul", "Langevin", 48);
// هوية هذا الشخص
print "personne=$p\n";
// تغيير العمر
$p->setAge(14);
// هوية الشخص
print "personne=$p\n";
// النهاية
exit;

النتائج:

personne=[Paul,Langevin,48]
personne=[Paul,Langevin,14]

تعليقات

  • الأسطر 6-50: فئة Personne؛
  • الأسطر 7-9: السمات الخاصة (private) للفئة. هذه السمات لا يمكن رؤيتها إلا داخل الفئة. الكلمات الرئيسية الأخرى التي يمكن استخدامها هي:
  • public: تجعل السمة مرئية من خارج الفئة،
  • protected: يجعل السمة protégé مرئية من داخل الفئة والفئات المشتقة منها؛
  • نظرًا لأن السمات خاصة، فلا يمكن الوصول إليها من خارج الفئة. وبالتالي، لا يمكن كتابة الكود التالي:
$p=new Personne() ;
$p->nom="Landru" ;

نحن هنا خارج الفئة Personne. وبما أن السمة nom خاصة، فإن السطر 2 غير صحيح. لتهيئة الحقول الخاصة للكائن $p، هناك طريقتان:

  • استخدام الطرق العامة set و get (يمكن أن يكون اسم هاتين الطريقتين أي اسم) في الأسطر 12-34. يمكننا عندئذٍ كتابة:
$p=new Personne() ;
$p->setNom("Landru") ;
  • استخدام منشئ الأسطر 37-42. عندئذٍ نكتب:
$p=new Personne("Michel","Landru",44) ;

الكتابة أعلاه تستدعي تلقائيًا طريقة الفئة Personne المسماة __construct؛

  • السطر 59: يعرض هذا السطر الكائن $p في شكل سلسلة أحرف. وللقيام بذلك، تُستخدم طريقة الفئة Personne المسماة __toString (الأسطر 45-47)؛
  • تمت إضافة الكلمة الرئيسية public كبادئة لجميع طرق الفئة (الوظائف) لتشير إلى أن الوظيفة مرئية خارج الفئة. الكلمات الرئيسية الأخرى التي يمكن استخدامها هي، كما هو الحال بالنسبة للسمات وبنفس المعنى، private و protected. بدون سمة رؤية صريحة، تكون الوظيفة ذات رؤية ضمنية عامة؛

5.6. فئة Personne مع عناصر التحقق من الصحة في المنشئ

يُعد منشئ الفئة المكان المناسب للتحقق من صحة قيم تهيئة الكائن. لكن يمكن أيضًا تهيئة الكائن من خلال أساليبه set أو ما يعادلها. لتجنب وضع نفس عمليات التحقق في مكانين مختلفين، يمكن وضعها في الأساليب set. إذا تبين أن إحدى قيم التهيئة للكائن غير صحيحة، فسيتم إلقاء استثناء. وفيما يلي مثال على ذلك.

نقوم أولاً بنقل تعريف الفئة Personne إلى ملفها الخاص [Personne.php]:


<?php

// الالتزام الصارم بأنواع المعلمات المعلنة للدوال
declare (strict_types=1);

// مساحة الأسماء؛
namespace Exemples;

// فئة «Personne»
class Personne {
// سمات الفئة
  private $prenom;
  private $nom;
  private $age;

// دالات الاسترجاع والتعيين
  public function getPrenom(): string {
    return $this->prenom;
  }

  public function getNom(): string {
    return $this->nom;
  }

  public function getAge(): int {
    return $this->age;
  }

  public function setPrenom(string $prénom): void {
    // يجب ألا يكون الاسم الأول فارغًا
    $prénom = trim($prénom);
    if ($prénom === "") {
      throw new \Exception("Le prénom doit être non vide");
    } else {
      $this->prenom = $prénom;
    }
  }

  public function setNom(string $nom): void {
    // يجب ألا يكون الاسم فارغًا
    $nom = trim($nom);
    if ($nom === "") {
      throw new \Exception("Le nom doit être non vide");
    } else {
      $this->nom = $nom;
    }
  }

  public function setAge(int $âge): void {
    // يجب أن يكون العمر صالحًا
    if ($âge < 0) {
      throw new \Exception("L'âge doit être un entier positif ou nul");
    } else {
      $this->age = $âge;
    }
  }

// منشئ
  public function __construct(string $prenom, string $nom, int $age) {
    // نستخدم طرق التعيين
    $this->setPrenom($prenom);
    $this->setNom($nom);
    $this->setAge($age);
  }

  // طريقة
  public function initWithPersonne(Personne $p): void {
    // تُهيئ الكائن الحالي بشخص ما $p
    $this->__construct($p->prenom, $p->nom, $p->age);
  }

  // طريقة toString
  function __toString(): string {
    return "[$this->prenom,$this->nom,$this->age]";
  }

}

تعليقات

  • السطر 4: يُطلب الالتزام بنوع معلمات الدوال عند إعلانها؛
  • السطر 7: يحدد مساحة أسماء (namespace). يصبح الاسم الكامل (المشار إليه بـ qualifié) للفئة Personne هو \Exemples\Personne. لاحظ الحرف \ الذي يبدأ به الاسم المؤهل: نحصل عندئذٍ على اسم مؤهل هو absolu. إذا كان هذا الحرف غائبًا، نحصل على اسم مؤهل هو relatif (مرتبط بمساحة الأسماء الحالية). وبالتالي، إذا كانت الفئتان A و B جزءًا من نفس مساحة الأسماء E، فيمكن الوصول إلى الفئة B في كود الفئة A باستخدام الترميز relative B. إذا كانت الفئة A جزءًا من مساحة الأسماء E1 و B جزءًا من مساحة الأسماء E2، ففي كود A، يمكن الوصول إلى B باستخدام الترميز absolue \E2\B. تعريف فئة داخل مساحة أسماء ليس إلزاميًا، لكن NetBeans يصدر تحذيرًا في حالة عدم القيام بذلك. لذا سنقوم بذلك. علاوة على ذلك، يجب أن تتوافق مساحات الأسماء مع هيكل شجرة الملفات. وبالتالي، يجب أن تكون الفئة A في مساحة الاسم E1 موجودة في ملف E1/A.php. هذا ليس إلزاميًا، ولكن مرة أخرى، يصدر Netbeans تحذيرًا إذا لم يتم ذلك. في المثال الخاص بالفئة [\Exemples\Personne]، يصدر NetBeans تحذيرًا لأن شجرة الملف [Personne.php] هي [exemples/classes/Personne.php]، وبالتالي لا تتطابق مع مساحة الاسم. يجب عدم الخلط بين شجرة الملفات ومساحة الأسماء. يستخدم الاسم الكامل لفئة ما مساحة أسماء ولا علاقة له بشجرة الملفات لملف الفئة PHP). العلاقة بين شجرة الملفات ومساحة الأسماء اختيارية وقد لا يتم الالتزام بها، كما فعلنا هنا؛
  • الأسطر 12-14: السمات الخاصة الثلاث للفئة؛
  • الأسطر 29-37: تهيئة السمة prenom والتحقق من قيمة التهيئة؛
  • السطر 31: تقوم الدالة trim($chaine) بإزالة المسافات الموجودة في بداية ونهاية $chaine. وبالتالي فإن trimabcd «) تساوي السلسلة «abcd»، وtrim« « تساوي السلسلة الفارغة؛
  • السطر 32: إذا كان الاسم الأول فارغًا، يتم إثارة استثناء (السطر 33)، وإلا يتم تخزين الاسم الأول (السطر 35). لإثارة استثناء، تم هنا استخدام الفئة المُعرَّفة مسبقًا [Exception]. ونحن مضطرون هنا إلى استخدام اسمها المطلق [\Exception]. وإذا استخدمنا اسمها النسبي [Exception]، فسيتم البحث عن هذه الفئة في مساحة الأسماء الحالية، أي مساحة الأسماء [Exemples] التابعة للفئة Personne. وبالتالي، سيبحث المُترجم PHP عن فئة ذات اسم مطلق [\Exemples\Exception] غير موجودة؛

يتم استخدام الفئة [Personne] بواسطة البرنامج النصي [classes-05.php] التالي:


<?php

// الالتزام الصارم بنوع معلمات الدوال
declare(strict_types=1);

// تضمين تعريف فئة Personne
require_once __DIR__."/Personne.php";

// الاسم المؤهل لفئة Personne
use \Exemples\Personne;

// اختبار
// إنشاء كائن من فئة «Personne»
$p = new Personne("Paul", "Langevin", 48);
// هوية هذا الشخص
print "Exemple1, personne=$p\n";
// إنشاء كائن «Personne» خاطئ
try {
  $p = new Personne("xx", "yy", "zz");
} catch (\Exception $e1) {
  print "Exemple2, erreur : " . $e1->getMessage() . "\n";
} catch (\TypeError $e2) {
  print "Exemple2, erreur : " . $e2->getMessage() . "\n";
}
// إنشاء كائن «Personne» غير صحيح
try {
  $p = new Personne("", "yy", 10);
} catch (\Exception $e1) {
  print "Exemple3, erreur : " . $e1->getMessage() . "\n";
} catch (\TypeError $e2) {
  print "Exemple3, erreur : " . $e2->getMessage() . "\n";
}

// النهاية
exit;

تعليقات

  • السطر 7: سيستخدم البرنامج النصي الفئة [Personne]. لذا، يجب إخبار المُفسر PHP بمكان العثور على تعريف هذه الفئة. وهذا هو دور التعليمات [include fichier] و [require fichier]. وقد استُخدمت هنا التعليمات [include]. الفرق بين الأمرين هو التالي: إذا واجه الأمر [include fichier] أخطاءً أثناء تحميل [fichierيتم إصدار خطأ من المستوى [E_WARNING]، لكنيستمر التنفيذ، بينما يولد الأمر [require] في الحالة نفسها خطأً فادحًا ويتوقف تنفيذ البرنامج النصي. لكل من هذين الأمرين متغيران هما [include_once] و [require_once]. تسمح هاتان المتغيرتان بمعالجة حالات التضمين المتعدد لنفس الملف. يمكننا هنا تصور مشروع يتكون من عدة نصوص برمجية PHP، حيث يشير العديد منها إلى الفئة [Personne]. سيؤدي تنفيذها إذن إلى تضمين الملف [Personne.php] عدة مرات، مما يتسبب في حدوث خطأ لأن الفئة لا يمكن تعريفها مرتين. الحل هو استخدام المتغيرات [_once] التي تضمن أن الملف لن يتم تضمينه سوى مرة واحدة في البرنامج النصي العام للمشروع؛
  • السطر 7: الثابت [__DIR__] هو ثابت PHP الذي يشير إلى الاسم الكامل للمجلد الذي يوجد فيه البرنامج النصي الذي يحتوي على الثابت [__DIR__]. وبالتالي، فإن التعبير الوارد في السطر 17:

require_once __DIR__."/Personne.php";

ستكون معادلة لشيء مثل:


require_once ‘C:\Data\st-2019\dev\php7\php5-exemples\exemples\classes/Personnes.php’

في مسار الملف، يمكن استخدام الرموز / و \ بشكل متبادل؛

  • السطر 14: نستخدم الفئة [Personne] التي حددناها للتو. لا يحتوي البرنامج النصي [classes-05.php] على مساحة أسماء. يستخدم السطر 14 الاسم النسبي للفئة [Personne] بدون مساحة أسماء. في حالة عدم وجود مساحة اسم للفئة [Personne]، يتم البحث عنها في البرنامج النصي [classes-05.php] نفسه، وبالتالي لن يتم العثور عليها. هناك حلان لهذه المشكلة:
    • استخدام الاسم الكامل للفئة [\Exemples\Personne]؛
    • استخدام الأمر use في السطر 10. يشير هذا الأمر إلى أن الكود الذي يليه يستخدم الفئة [\Exemples\Personne]؛
  • السطر 10: تسمح تعليمة use للمترجم بمعرفة أن الفئة [Personne] المشار إليها في السطر 14 هي في الواقع الفئة [\Exemples\Personne]. ومع ذلك، أين سيجد المترجم كود هذه الفئة؟ السطر 7 هو الذي يحدد ذلك. يشير هذا السطر إلى أنه لتنفيذ البرنامج النصي الحالي، يجب أيضًا تحميل البرنامج النصي [Personne.php]. وقد استُخدم هنا الاسم النسبي للملف. وبالتالي، سيتم البحث عنه في المجلد الذي يحتوي على البرنامج النصي [classes-05.php]. لذلك يجب أن يكون البرنامجان النصيان [Personne.php] و [classes-05.php] في نفس المجلد. وهذا هو الحال هنا حيث يوجد كلاهما في المجلد [exemples/classes]. التعليمات الواردة في السطر 10 تعادل:

use \Exemples\Personne as Personne;

تشير التعليمات [use] أعلاه إلى أن الاسم المستعار [Personne] يشير إلى الفئة [\Exemples\Personne]؛

  • السطر 14: يتم إنشاء كائن [Personne]. وسيتم هنا تنفيذ الطريقة [__construct] الخاصة بالفئة [Personne] ضمناً؛
  • السطر 16: يعرض الشخص $p. لعرضها، يجب تحويل قيمة المتغير $p إلى سلسلة أحرف. ضمناً، يتم عندئذ تنفيذ الطريقة [Personne.__toString]. لذا يجب أن تُرجع هذه الطريقة سلسلة أحرف؛
  • وقد رأينا أن مُنشئ الفئة [Personne] يمكن أن يُطلق استثناءً من النوع [\Exception]. لذلك يجب معالجتها. كما أن كود السطر 14 غير مكتمل. يجب استخدام كود الأسطر 18-24 لمعالجة الاستثناء الذي قد يحدث بشكل صحيح. هنا ننتج استثناءً عن قصد بتمرير عمر ليس عددًا صحيحًا. في هذه الحالة بالذات، يتم إطلاق الاستثناء الذي يحدث بواسطة مترجم PHP وليس بواسطة كود فئة [Personne]. في الواقع، توقيع طريقة [Personne.__construct] هو كما يلي:

function __construct(string $prenom, string $nom, int $age)

لذلك، يجب أن يكون المعلمة [age] التي يتم تمريرها إلى المُنشئ من النوع «عدد صحيح». وإذا لم يكن الأمر كذلك، فإن المُفسر PHP يُطلق خطأً من النوع [TypeError]. من ناحية أخرى، فإن الأساليب [set] التابعة للفئة [Personne] تُطلق استثناءً من النوع [\Exception]. ونظرًا لأن المنشئ الذي يستدعيها لا يحتوي على بنية try / catch، فإن الاستثناء ينتقل إلى المستوى الأعلى، إلى الكود الذي استدعى المنشئ، أي كود البرنامج النصي [classes-05.php]. وأخيرًا، يمكن أن يتلقى البرنامج النصي [classes-05.php] نوعين من الاستثناءات: \Exception أو \TypeError. تجدر الإشارة إلى أنه عندما يكون المطور متأكدًا من أن بعض الاستثناءات لا يمكن أن تحدث، فإنه لن يستخدم خيارات catch المقابلة. هنا يتم استخدامها بشكل منهجي لغرض التوضيح فقط. ومع ذلك، سيتم استخدام خيارات catch لأي استثناء محتمل، حتى لو كان احتمال حدوثه ضئيلًا؛

ولهذا السبب، تحتوي بنية try في الأسطر 18-24 على جملتي catch لمعالجة نوعي الاستثناءات بشكل منفصل؛

  • السطر 20: يمكن كتابة [Exception] أو [\Exception] دون فرق:
    • تستخدم النسخة الأولى الاسم النسبي للفئة، بالنسبة لمساحة أسماء البرنامج النصي. وهذا البرنامج النصي لا يحتوي على مساحة أسماء. لذا فإن مساحة أسماءه هي جذر مساحات الأسماء: \. وبالتالي، فإن كتابة [Exception] هنا تعادل كتابة [\Exception]. ومع ذلك، فإن الفئة [Exception] موجودة بالفعل في مساحة الأسماء [\]؛

من الأفضل استخدام الاسم المطلق للاستثناءات المُعرَّفة مسبقًا لـ PHP في برنامج نصي لا يحتوي هو نفسه على مساحات أسماء. وبالتالي، إذا قررنا إعطاء مساحة أسماء لهذا البرنامج النصي، فإن كتابة الأسماء المطلقة للفئات تظل صالحة، بينما في الحالة الأخرى، سيؤدي تغيير مساحة الأسماء إلى حدوث أخطاء في الأسماء النسبية للفئات؛

  • السطر 21: عند حدوث استثناء، تسمح الطريقة [Exception→getMessage] بالحصول على رسالة خطأ الاستثناء. وينطبق الأمر نفسه على خطأ من النوع [TypeError]. في الطريقة [Personne.setPrenom]، كُتب:

  public function setPrenom(string $prénom) {
    // يجب ألا يكون الاسم الأول فارغًا
    $prénom = trim($prénom);
    if ($prénom === "") {
      throw new \Exception("Le prénom doit être non vide");
    } else {
      $this->prenom = $prénom;
    }
}

في السطر 5، يتم إلقاء استثناء مع رسالة الخطأ [Le prénom doit être non vide]. وهذا ما ستسترده الطريقة [Exception→getMessage] من السطر 29 من البرنامج النصي [classes-05.php].

النتائج:


Exemple1, personne=[Paul,Langevin,48]
Exemple2, erreur : Argument 3 passed to Exemples\Personne::__construct() must be of the type integer, string given, called in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_18.php on line 19
Exemple3, erreur : Le prénom doit être non vide

5.7. إضافة دالة تعمل كمنشئ ثانٍ

في PHP 7، لا يمكن وجود عدة منشئات بمعلمات مختلفة تسمح بإنشاء كائن بطرق متنوعة. يمكن عندئذ استخدام طرق تعمل كمنشئ:


  // طريقة
  public function initWithPersonne(Personne $p) {
    // تقوم بتهيئة الكائن الحالي بشخص $p
    $this->__construct($p->prenom, $p->nom, $p->age);
}

تعليقات

  • الأسطر 2-5: تسمح الطريقة initWithPersonne بتعيين قيم سمات كائن آخر Personne إلى الكائن الحالي. وهنا، تستدعي هذه الطريقة مُنشئ __construct، لكن هذا ليس إلزاميًا. فهي يمكنها تهيئة سمات الفئة [Personne] بنفسها؛

يتم استخدام الفئة الجديدة [Personne] بواسطة البرنامج النصي [classes-06.php] التالي:


<?php

// تضمين تعريف فئة «Personne»
require_once __DIR__."/Personne.php";
// إعلان الاسم المؤهل لفئة «Personne»
use \Exemples\Personne;

// اختبار
// إنشاء كائن Personne
try {
  $p = new Personne("Paul", "Langevin", 48);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
// هوية هذا الشخص
print "personne=$p\n";
// إنشاء شخص ثانٍ
try {
  $p2 = new Personne("Laure", "Adeline", 67);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
// تهيئة الشخص الأول بقيم الشخص الثاني
try {
  $p->initWithPersonne($p2);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}

// التحقق
print "personne=$p\n";
// النهاية
exit;
  • الأسطر 14 و23 و30: غالبًا ما يتعين إيقاف تنفيذ البرنامج النصي الخاص بوحدة التحكم بعد حدوث استثناء إذا كان الخطأ الذي تمت مواجهته غير قابل للاسترداد. ولكن الأمر يختلف في البرنامج النصي على الويب: لا يتم إيقاف تنفيذ البرنامج النصي، بل يتم عرض صفحة خطأ. وإذا كنا داخل دالة، فلن يتم استخدام الأمر exit بل الأمر return: لا يتم إيقاف تنفيذ البرنامج النصي (exit) بل يتم الخروج من الدالة (return) بعد تحديد خطأ؛

النتائج:

personne=[Paul,Langevin,48]
personne=[Laure,Adeline,67]

5.8. مصفوفة من الكائنات [Personne]

يوضح المثال التالي [classes-07.php] أنه يمكننا الحصول على مصفوفات من الكائنات.


<?php

require_once __DIR__."/Personne.php";
use \Exemples\Personne;

// اختبار
// إنشاء مصفوفة من كائنات «Personne»
// لتسهيل فهم الكود، لا يتم التعامل مع الاستثناء المحتمل
$groupe = [new Personne("Paul", "Langevin", 48), new Personne("Sylvie", "Lefur", 70)];

// هوية هؤلاء الأشخاص
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}

// النهاية
exit;

النتائج:

groupe[0]=[Paul,Langevin,48]
groupe[1]=[Sylvie,Lefur,70]

تعليقات

  • السطر 9: إنشاء مصفوفة من شخصين؛
  • السطر 12: مسار الجدول؛
  • السطر 13: $groupe[$i] هو كائن من النوع Personne. تُستخدم الطريقة [Personne.__toString] لعرضه؛

5.9. إنشاء فئة مشتقة من فئة Personne

نقوم بإنشاء الفئة [Enseignant] التالية في ملف [Enseignant.php]:


<?php

// الالتزام الصارم بأنواع المعلمات المعلنة للدوال
declare (strict_types=1);

// مساحة الأسماء
namespace Exemples;

// فئة مشتقة من person
class Enseignant extends Personne {
  // السمات
  private $discipline;   // المادة التي يُدرَّسها

  // أدوات الحصول والتعيين

  public function getDiscipline(): string {
    return $this->discipline;
  }

  public function setDiscipline(string $discipline): void {
    $this->discipline = $discipline;
  }

  // منشئ
  public function __construct(string $prénom, string $nom, int $âge, string $discipline) {
    // السمات الأبوية
    parent::__construct($prénom, $nom, $âge);
    // السمات الأخرى
    $this->setDiscipline($discipline);
  }

  // تحميل الدالة __toString الخاصة بالفئة الأصلية
  public function __toString(): string {
    return "[" . parent::__toString() . ",$this->discipline]";
  }

}

تعليقات

  • السطر 7: الفئة [Enseignant] تنتمي هي الأخرى إلى مساحة الأسماء [Exemples]؛
  • السطر 10: الفئة Enseignant مشتقة (تتوسع) من الفئة Personne.. الفئة المشتقة Enseignant ترث السمات والأساليب من فئتها الأم؛
  • السطر 12: تضيف الفئة Enseignant سمة جديدة خاصة بها هي discipline؛
  • السطر 25: يتلقى منشئ الفئة Enseignant 4 معلمات:
    • 3 لتهيئة فئتها الأم (الاسم الأول، الاسم الأخير، العمر)، السطر 27؛
    • 1 لتهيئة نفسها (التخصص)، السطر 29؛
  • السطر 27: يمكن للفئة المشتقة الوصول إلى الأساليب ومنشئات الفئة الأم عبر الكلمة الرئيسية parent ::. هنا يتم تمرير المعلمات (الاسم الأول، الاسم الأخير، العمر) إلى منشئ الفئة الأم؛
  • الأسطر 33-35: تستخدم الطريقة __toString للفئة المشتقة الطريقة __toString للفئة الأصلية؛

يتم استخدام الفئة [Enseignant] بواسطة البرنامج النصي [classes-08.php] التالي:


<?php

// تضمين تعريف الفئتين
require_once __DIR__."/Personne.php";
require_once __DIR__."/Enseignant.php";
// إعلان الفئتين المستخدمتين
use \Exemples\Personne;
use \Exemples\Enseignant;

// اختبار
// إنشاء مصفوفة من كائنات Personne والمشتقات منها
// لتبسيط المثال، لا يتم التعامل مع الاستثناءات
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70));

// هوية هؤلاء الأشخاص
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
// النهاية
exit;

تعليقات

  • السطران 4-5: علينا أن نخبر المترجم PHP بمكان وجود الفئتين [Enseignant, Personne]؛
  • السطران 7-8: إعلان الأسماء الكاملة للفئتين. سيسمح لنا ذلك بتحديدهما في الكود ببساطة بأسمائهما دون لاحقة مساحات الأسماء الخاصة بهما؛
  • السطر 13: ننشئ مصفوفة تحتوي على نوع [Personne] ونوع [Enseignant]؛
  • السطور 16-18: تعرض عناصر المصفوفة؛
  • السطر 17: سيتم استدعاء الطريقة __toString لكل عنصر من عناصر $groupe[$i]. تحتوي الفئة Personne على طريقة __toString. تحتوي الفئة Enseignant على طريقتين: طريقة الفئة الأم وطريقتها الخاصة. قد يتساءل المرء أيهما سيتم استدعاؤها. يوضح التنفيذ أن الطريقة التي تم استدعاؤها هي تلك الخاصة بالفئة Enseignant. وهذا هو الحال دائمًا: عندما يتم استدعاء دالة على كائن ما، يتم البحث عنها بالترتيب التالي: في الكائن نفسه، ثم في فئته الأصلية إن وجدت، ثم في الفئة الأصلية للفئة الأصلية، وهكذا دواليك… ويتوقف البحث بمجرد العثور على الدالة.

النتائج:

groupe[0]=[[Paul,Langevin,48],anglais]
groupe[1]=[Sylvie,Lefur,70]

5.10. إنشاء فئة ثانية مشتقة من فئة Personne

يُنشئ المثال التالي فئة Etudiant مشتقة من الفئة Personne، في الملف [Etudiant.php]:


<?php

// الالتزام الصارم بأنواع المعلمات المعلنة للدوال
declare (strict_types=1);

// مساحة الأسماء
namespace Exemples;

class Etudiant extends Personne {
  // السمات
  private $formation;   // الدورات التدريبية التي تم حضورها

  // الوصول إلى القيم (getter) وتعيين القيم (setter)
  public function getFormation(): string {
    return $this->formation;
  }

  public function setFormation(string $formation): void {
    $this->formation = $formation;
  }

  // منشئ
  public function __construct(string $prénom, string $nom, int $âge, string $formation) {
    // السمات الأبوية
    parent::__construct($prénom, $nom, $âge);
    // السمات الأخرى
    $this->setFormation($formation);
  }

  // إعادة تعريف الدالة __toString للفئة الأصلية
  public function __toString(): string {
    return "[" . parent::__toString() . ",$this->formation]]";
  }

}

يتم استخدام هذه الفئة بواسطة البرنامج النصي [classes-09.php] التالي:


<?php

// تضمين وتعريف الفئات المستخدمة في البرنامج النصي
require_once __DIR__."/Personne.php";
use \Exemples\Personne;
require_once __DIR__."/Enseignant.php";
use \Exemples\Enseignant;
require_once __DIR__."/Etudiant.php";
use \Exemples\Etudiant;

// اختبار
// إنشاء مصفوفة من كائنات person والمشتقات
// لتسهيل فهم المثال، لا يتم التعامل مع الاستثناءات
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70), new Etudiant("Steve", "Boer", 23, "iup2 qualité"));

// هوية هؤلاء الأشخاص
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
// النهاية
exit;

النتائج:


groupe[0]=[[Paul,Langevin,48],anglais]
groupe[1]=[Sylvie,Lefur,70]
groupe[2]=[[Steve,Boer,23],iup2 qualité]

5.11. علاقة منشئ الفئة المشتقة بمنشئ الفئة الأصلية

في بعض اللغات الموجهة للكائنات، يقوم منشئ الفئة المشتقة تلقائيًا باستدعاء منشئ الفئة الأم. يوضح الكود التالي [classes-16.php] أنه مع PHP 7، لا يحدث ذلك:


<?php

class Classe1 {

  // منشئ
  public function __construct() {
    print "constructeur de la classe Classe1\n";
  }

}

class Classe2 extends Classe1 {

  // منشئ
  public function __construct() {
    // لا يتم استدعاء منشئ الفئة الأم ضمناً
    print "constructeur de la classe Classe2\n";
  }

}

class Classe3 extends Classe1 {

  // منشئ
  public function __construct() {
    // استدعاء صريح لمنشئ الفئة الأصلية
    parent::__construct();
    // كود خاص بـ Classe3
    print "constructeur de la classe Classe3\n";
  }

}

// اختبارات
print "test1---------\n";
new Classe2();
print "test2---------\n";
new Classe3();

النتائج

1
2
3
4
5
test1---------
constructeur de la classe Classe2
test2---------
constructeur de la classe Classe1
constructeur de la classe Classe3

5.12. إعادة تعريف طريقة في الفئة الأصلية

لقد رأينا سابقًا أنه يمكن إعادة تعريف إحدى طرق الفئة الأم في فئة فرعية. وهكذا، تمت إعادة تعريف الطريقة [__toString] الخاصة بالفئة [Personne] (انظر الرابط) في الفئتين الفرعيتين [Enseignant] (انظر الرابط) و [Etudiant] (انظر الرابط). يوضح البرنامج النصي [classes-13.php] هذا المفهوم مرة أخرى:


<?php

// الالتزام الصارم بنوع معلمات الدوال
declare(strict_types=1);

// الفئة الرئيسية
class Classe1 {

  public function f(): int {
    return 1;
  }

  function g(): int {
    return 2;
  }

}

// الفئة المشتقة
class Classe2 extends Classe1 {

  // إعادة تعريف الدالة f للفئة الأصلية
  public function f(): int {
    return parent::f() + 10;
  }

}

// الكود
$c2 = new Classe2();
print $c2->f() . "\n";
print $c2->g() . "\n";
$c1=new Classe1();
print $c1->f()."\n";

تعليقات

  • الأسطر 7-17: تُعرِّف الفئة [Classe1] طريقتين هما f و g؛
  • الأسطر 20-27: الفئة [Classe2] توسع الفئة [Classe1] وتعيد تعريف الطريقة f الخاصة بها؛

النتائج

1
2
3
11
2
1

تعليقات

  • السطر 30 من الكود ينشئ كائن $c2 من النوع [Classe2]؛
  • السطر 31 من الكود يستدعي الطريقة f للكائن $c2. وبما أن هذه الطريقة موجودة، يتم تنفيذها؛
  • السطر 32 من الكود يستدعي الطريقة g للكائن $c2. وبما أن هذه الطريقة غير موجودة، يتم البحث عنها في الفئة الأم حيث يتم العثور عليها وتنفيذها؛
  • السطر 33 من الكود ينشئ كائن $c1 من النوع [Classe1]؛
  • السطر 34 من الكود يستدعي الطريقة f للكائن $c1. وبما أن هذه الطريقة موجودة، يتم تنفيذها؛

5.13. تمرير كائن كمعلمة لدالة

لننظر إلى البرنامج النصي [classes-14.php] التالي:


<?php

// الالتزام الصارم بنوع معلمات الدوال
declare(strict_types=1);

// الفئة الرئيسية
class Classe1 {

  public function f(): int {
    return 1;
  }

  function g(): int {
    return 2;
  }

}

// الفئة المشتقة
class Classe2 extends Classe1 {

  // إعادة تعريف الدالة f للفئة الأصلية
  public function f(): int {
    return parent::f() + 10;
  }

}

// معلمة الدالة من نوع «Fئة1» أو مشتقة منها
function doSomething(Classe1 $c1): void {
  print $c1->f() + $c1->g() . "\n";
}

// كود
// يتم إنشاء كائن من النوع Classe2 مشتق من Classe1
$c2 = new Classe2();
// يتم استدعاء doSomething باستخدام
doSomething($c2);

تعليقات

  • الأسطر 7-17: الفئة [Classe1]؛
  • الأسطر 20-27: فئة [Classe2] مشتقة من [Classe1]؛
  • السطر 30: دالة تتوقع معلمة من النوع [Classe1]. عندما يكون النوع المتوقع فئة، يمكن أن تكون المعلمة الفعلية كائنًا من النوع المتوقع أو مشتقًا منه؛
  • الأسطر 35-38: يتم استدعاء الدالة [doSomething] بمعلمة من النوع [Classe2]، في حين أن النوع المتوقع هو [Classe1]؛

النتائج

13

5.14. الفئات المجردة

الفئة المجردة هي فئة غير مكتملة لا يمكن إنشاء مثيل لها. ويجب بالضرورة أن تكون مشتقة حتى يمكن استخدامها.

ما فائدة الفئة المجردة؟ أحيانًا تكون لدينا فئات تشترك في طريقة أو أكثر ولكنها تختلف في طرق أو سمات أخرى. ومن المستحسن في هذه الحالة تجميع كل ما هو مشترك في فئة أصلية. في الوقت الحالي، لا نحتاج إلى فئة مجردة. لكن لنفترض أن الفئات الفرعية لا تختلف إلا في طريقة M: سيكون توقيع الطريقة هو نفسه في جميع الفئات الفرعية، لكن تنفيذها سيختلف. لإجبار الفئات الفرعية على تنفيذ الطريقة M:

  • سنقوم بإعلان توقيع الطريقة M في الفئة الأم. وبما أن هذه الفئة لا تعرف كيفية تنفيذها، فإننا نضع الكلمة المفتاحية abstract كبادئة للطريقة: وهذا يعني أن تنفيذ الطريقة M يُحال إلى الفئات الفرعية؛
  • ونظرًا لأن الفئة الأم ليست مُنفَّذة بالكامل، فإنها تُعلن أيضًا على أنها مجردة باستخدام نفس الكلمة المفتاحية abstract. وهذا يجعل من المستحيل إنشاء مثيل للفئة. لذا، من الضروري إنشاء فئة فرعية تحدد تنفيذ الدالة M، حتى يصبح جسم الفئة الأم قابلاً للاستخدام؛

فيما يلي مثال [classes-15.php]:


<?php

// الالتزام الصارم بنوع معلمات الدوال
declare(strict_types=1);

// فئة رئيسية مجردة
abstract Class Classe1 {

  // طريقة معروفة لجميع الفئات المشتقة
  public function f(): int {
    return 1;
  }

  // طريقة g المجردة - سيتم تعريفها بواسطة الفئات المشتقة
  abstract function g(): int;
}

// فئة مشتقة
Class Classe2 extends Classe1 {

  // يجب تعريف الطريقة g للفئة الأصلية
  public function g(): int {
    return parent::f() + 10;
  }

}

// فئة مشتقة
Class Classe3 extends Classe1 {

  // يجب تعريف الطريقة g للفئة الأصلية
  public function g(): int {
    return parent::f() + 20;
  }

  // يمكن إعادة تعريف الطريقة f للفئة الأصلية
  public function f(): int {
    return 2;
  }

}

// رمز
$c2 = new Classe2();
print $c2->f() . "\n";
print $c2->g() . "\n";
$c3 = new Classe3();
print $c3->f() . "\n";
print $c3->g() . "\n";

تعليقات

  • الأسطر 7-16: الفئة [Classe1] هي فئة مجردة (السطر 7) لأنها لا تعرف كيفية تنفيذ الطريقة g الموجودة في السطر 15. لذا يجب أن تكون مشتقة حتى يمكن استخدامها؛
  • الأسطر 19-26: الفئة [Classe2] تمتد من الفئة [Classe1] وتعيد تعريف الطريقة g الخاصة بفئتها الأم (الأسطر 22-24)؛
  • الأسطر 29-41: الفئة [Classe3] توسع الفئة [Classe1] وتعيد تعريف الطريقة g لفئتها الأم (الأسطر 32-34)؛
  • الأسطر 37-39: تعيد الفئة [Classe3] تعريف الطريقة f لفئتها الأم؛
  • الأسطر 44-49: يتم إنشاء كائنين من النوع [Classe2] و [Classe3] ويتم استدعاء طريقتيهما f و g؛

النتائج

1
2
3
4
1
11
2
21

5.15. الفئات النهائية

الفئة النهائية هي فئة لا يمكن اشتقاق فئات منها. لننظر إلى البرنامج النصي [classes-11.php] التالي:


<?php

// مساحة الأسماء
namespace Exemples;

// فئة غير قابلة للاشتقاق
final Class Classe1 {
  
}

// فئة مشتقة
Class Classe2 extends Classe1 {
  
}

// الكود - يجب أن يتسبب في حدوث خطأ
new Classe2();

تعليقات

  • الأسطر 7-9: الكلمة الرئيسية final تجعل من الفئة [Classe1] فئة نهائية لا يمكن اشتقاقها؛
  • الأسطر 12-14: الفئة [Classe2] تمتد من الفئة النهائية [Classe1]، وهذا خطأ؛
  • السطر 17: لن يتم الإبلاغ عن الخطأ إلا عند تنفيذ البرنامج النصي عند محاولة التعامل مع كائن من النوع [Classe2]؛

النتائج

Fatal error: Class Exemples\Classe2 may not inherit from final class (Exemples\Classe1) in C:\Data\st-2019\dev\php7\php5-exemples\exemples\classes\classes-11.php on line 14

5.16. الطرق النهائية

الطريقة النهائية هي طريقة لا يمكن إعادة تعريفها عن طريق التوريث. فيما يلي مثال على ذلك: [classes-12.php]:


<?php

// الالتزام الصارم بنوع معلمات الدوال
declare(strict_types=1);

// مساحة الأسماء
namespace Exemples;

// الفئة الرئيسية
Class Classe1 {

  // لا يمكن إعادة تعريف هذه الطريقة في فئة مشتقة
  public final function f(): int {
    return 1;
  }

}

// فئة مشتقة
Class Classe2 extends Classe1 {

  public function f(): int {
    return 2;
  }

}

// كود - يجب أن يتسبب في حدوث خطأ
new Classe2();

تعليقات

  • السطر 13: تم إعلان الطريقة f للفئة [Classe1] على أنها نهائية باستخدام الكلمة المفتاحية final؛
  • السطر 20: الفئة [Classe2] تمتد من الفئة [Classe1]؛
  • السطران 22-23: يتم إعادة تعريف الدالة f للفئة الأم [Classe1]. ومن المفترض أن يؤدي ذلك إلى حدوث خطأ؛
  • السطر 29: يتم إنشاء كائن من النوع [Classe2] لإجبار المُفسر PHP على فحص الفئة [Classe2]؛

النتائج

Fatal error: Cannot override final method Exemples\Classe1::f() in C:\Data\st-2019\dev\php7\php5-exemples\exemples\classes\classes-12.php on line 26

5.17. الطرق والسمات الثابتة

الطريقة الثابتة هي طريقة مرتبطة بالفئة التي تم تعريفها فيها وليس بالكائنات التي تم إنشاؤها من هذه الفئة. وبالتالي، إذا أعلنت الفئة C عن طريقة ثابتة M، فلكي نستخدم هذه الطريقة، سنكتب:

  • C::M إذا كنا خارج الفئة؛
  • self::M إذا كنا داخل الفئة؛

فيما يلي مثال [classes-17.php]:


<?php

class Classe1 {

  // طريقة ثابتة
  static function say(string $message): void {
    print "$message\n";
  }

}

// اختبار -------------------
Classe1::say("hello");

تعليقات

  • السطر 6: تم إعلان الطريقة [say] على أنها ثابتة باستخدام الكلمة المفتاحية static؛
  • السطر 13: استدعاء الطريقة الثابتة [say] بالصيغة: Classe1::say؛

النتائج

hello

لننظر الآن إلى الكود التالي [classes-18.php]:


<?php

class Classe1 {
  // سمة ثابتة
  private static $nbObjects = 0;

  public function __construct() {
    print "constructeur Classe1\n";
    self::$nbObjects++;
  }

  // طريقة ثابتة
  static function say(): void {
    print self::$nbObjects ." objets de type [Classe1] ont été construits\n";
  }

}

// اختبار -------------------
new Classe1();
new Classe1();
Classe1::say();

تعليقات

  • السطر 5: يتم إعلان سمة ثابتة ستقوم بحساب عدد مثيلات الفئة [Classe1] التي تم إنشاؤها. هذه ليست سمة يمكن أن تنتمي إلى مثيل من الفئة. في الواقع، إذا تم إنشاء كائنين O1 و O2، فلن يكون أي منهما على علم بالآخر. لا معنى لوجود عداد في المثيل: فعندما يتم إنشاء كائن جديد، في أي مثيل سيتم زيادة العداد؟ سنضطر إلى زيادة عداد كائن معين، متجاهلين عدادات المثيلات الأخرى. السمة الثابتة هي سمة للفئة وليست سمة لمثيل الفئة؛
  • الأسطر 7-10: يتم حساب الكائنات التي تم إنشاؤها في المنشئ، لأن إنشاء كل كائن جديد يؤدي إلى تنفيذ المنشئ؛
  • السطر 14: لاحظ الترميز self::$nbObjects للإشارة إلى أننا نشير إلى سمة ثابتة للفئة التي يوجد فيها الكود المنفذ؛
  • الأسطر 13-15: تتمثل وظيفة الطريقة الثابتة [say] في عرض عدد الكائنات التي تم إنشاؤها؛
  • الأسطر 20-22: يتم إنشاء كائنين وعرض عداد الكائنات؛

النتائج


constructeur Classe1
constructeur Classe1
2 objets de type [Classe1] ont été construits

5.18. الرؤية بين الفئة الأم والفئة التابعة

لنلقِ نظرة على البرنامج النصي [classes-19.php] التالي:


<?php

class SomeParent {
  // سمة
  private $attributeOfParent = 4;

  // طريقة
  public function doTest(): void {
    // من يستدعيها؟
    print "parent :\n";
    var_dump($this);
    // العرض الأصلي
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }

}

class SomeChild extends SomeParent {
  // السمة
  private $attributeOfChild = 14;

  // طريقة
  public function doTest(): void {
    // عرض التابع
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";

    // الأصل
    parent::doTest();
  }
}

// النص البرمجي الرئيسي
print "---test1\n";
(new SomeParent())->doTest();
print "---test2\n";
(new SomeChild())->doTest();

تعليقات

  • الأسطر 3-17: الفئة [SomeParent]؛
  • الأسطر 19-32: الفئة الفرعية [SomeChild]. نلاحظ أنها تمتد من الفئة [SomeParent] (السطر 19)؛
  • السطر 5: الفئة [SomeParent] لها سمة واحدة فقط؛
  • الأسطر 8-17: تهدف الطريقة [SomeParent::doTest] إلى عرض سمتين:
    • [$attributeOfParent] التي تنتمي إلى الفئة [SomeParent]؛
    • [$attributeOfChild] التي تنتمي إلى الفئة [SomeChild] (السطر 21)؛
  • السطران 10-11: يتم عرض هوية المتصل: سيتم بالفعل استدعاء الأسلوب بطريقتين مختلفتين:
    • من الفئة الأم [SomeParent]؛
    • من الفئة الفرعية [SomeChild]؛
  • السطران 13-14: عرض السمتين؛
  • السطور 19-32: الفئة الفرعية [SomeChild] التي تمتد من الفئة [SomeParent] (السطر 19)؛
  • السطر 21: الفئة [SomeChild] لها سمة واحدة فقط؛
  • السطر 24: تهدف الطريقة [SomeChild::doTest] إلى عرض سمتين:
    • [$attributeOfParent] التي تنتمي إلى الفئة [SomeParent]؛
    • [$attributeOfChild] التي تنتمي إلى الفئة [SomeChild]؛
  • السطران 26-27: عرض السمتين؛
  • السطر 30: استدعاء الأسلوب [doTest] من الفئة الأم، والذي سيقوم بدوره بعرض السمتين؛
  • السطر 36: يتم استدعاء الدالة [SomeParent::doTest]؛
  • السطر 38: يتم استدعاء الدالة [SomeChild::doTest]؛

في الاختبار الأول، تكون رؤية السمتين هي [private]. لذلك يمكننا توقع ألا ترى الفئة الفرعية السمة الخاصة بفئتها الأم. يجب أن يكون مستوى الرؤية لهذه السمة على الأقل هو [protected]. ولكن ماذا عن سمة الفئة الفرعية؟ هل هي مرئية في الفئة الأم؟

فيما يلي نتائج هذا الاختبار الأول:

---------------------------test1
parent :
object(SomeParent)#1 (1) {
  ["attributeOfParent":"SomeParent":private]=>
  int(4)
}
parent : attributeOfParent=4

Notice: Undefined property: SomeParent::$attributeOfChild in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php on line 14
parent : attributeOfChild=
---------------------------test2

Notice: Undefined property: SomeChild::$attributeOfParent in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php on line 26
child : attributeOfParent=
child : attributeOfChild=14
parent :
object(SomeChild)#1 (2) {
  ["attributeOfChild":"SomeChild":private]=>
  int(14)
  ["attributeOfParent":"SomeParent":private]=>
  int(4)
}
parent : attributeOfParent=4

Fatal error: Uncaught Error: Cannot access private property SomeChild::$attributeOfChild in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php:14
Stack trace:
#0 C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php(30): SomeParent->doTest()
#1 C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php(39): SomeChild->doTest()
#2 {main}
  thrown in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php on line 14

تعليقات

  • الأسطر 1-10: نتائج الاختبار الأول حيث يتم استدعاء الطريقة [SomeParent::doTest]؛
  • الأسطر 3-6: نرى أن الكائن الذي يستدعي الطريقة هو من النوع [SomeParent]؛
  • السطر 7: عرض السمة [$attributeOfParent]؛
  • السطور 9-10: نرى أن السمة [SomeParent::$attributeOfChild] غير موجودة. وبالتالي لا يتم عرضها؛
  • الأسطر 11-30: نتائج الاختبار الثاني حيث يتم استدعاء الدالة [SomeChild::doTest]؛
  • السطران 13-14: نلاحظ أن السمة [SomeChild::$attributeOfParent] غير موجودة. وبالتالي لا يتم عرضه. وهذا أمر طبيعي: السمة [SomeParent::$attributeOfParent] هي [private]، وبالتالي فهي غير معروفة في الفئة الفرعية؛
  • السطر 15: عرض السمة [$attributeOfChild]؛
  • الأسطر 16-30: نحن الآن في الأسلوب [SomeParent::doTest] الذي استدعته الفئة الفرعية؛
  • الأسطر 17-22: نلاحظ أن [$this] من النوع [SomeChild] مع سمتين خاصتين؛
  • السطر 23: بشكل مثير للدهشة، ترى [$this] من النوع [SomeChild] هنا سمة الأصل [$attributeOfParent]؛
  • الأسطر 25-30: وبشكل مثير للدهشة أيضًا، لا يظهر لـ [$this] من النوع [SomeChild] سمة [$attributeOfChild] الخاصة به؛

هذه النتيجة مدهشة للغاية: على الرغم من أن الأسطر 17-21 تشير إلى أن [$this] من النوع [SomeChild]، إلا أن [$this] الموجود داخل الأسلوب [SomeParent::doTest] يتصرف كما لو كان مثيلًا للفئة [SomeParent] وليس للفئة [SomeChild].

لنجرِ اختبارًا جديدًا باستخدام البرنامج النصي [classes-20.php]. أصبح للسمة [$attributeOfParent] الآن نطاق رؤية [protected] (السطر 5):


<?php

class SomeParent {
  // السمة
  protected $attributeOfParent = 4;

  // طريقة
  public function doTest(): void {
    // من يستدعي؟
    print "parent :\n";
    var_dump($this);
    // العرض الأصلي
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }

}

class SomeChild extends SomeParent {
  // السمة
  private $attributeOfChild = 14;

  // طريقة
  public function doTest(): void {
    // عرض التابع
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";

    // الأصل
    parent::doTest();
  }

}

// النص البرمجي الرئيسي
print "---------------------------test1\n";
(new SomeParent())->doTest();
print "---------------------------test2\n";
(new SomeChild())->doTest();

النتائج

---------------------------test1
parent :
object(SomeParent)#1 (1) {
  ["attributeOfParent":protected]=>
  int(4)
}
parent : attributeOfParent=4

Notice: Undefined property: SomeParent::$attributeOfChild in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-20.php on line 14
parent : attributeOfChild=
---------------------------test2
child : attributeOfParent=4
child : attributeOfChild=14
parent :
object(SomeChild)#1 (2) {
  ["attributeOfChild":"SomeChild":private]=>
  int(14)
  ["attributeOfParent":protected]=>
  int(4)
}
parent : attributeOfParent=4

Fatal error: Uncaught Error: Cannot access private property SomeChild::$attributeOfChild in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-20.php:14
Stack trace:
#0 C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-20.php(30): SomeParent->doTest()
#1 C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-20.php(39): SomeChild->doTest()
#2 {main}
  thrown in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-20.php on line 14

تعليقات

  • السطر 12: أصبحت الفئة [SomeChild] ترى الآن السمة الخاصة بوالدها [$attributeOfParent]. وهذا أمر طبيعي لأن نطاق هذا الأخير أصبح الآن [protected]؛
  • في الأسلوب [someParent::doTest]، يكون الكائن [$this] من النوع [SomeChild] (الأسطر 15-20). وهو يرى سمة أصلها [$attributeOfparent] (السطر 21) ولكنه لا يرى سمة [$attributeOfChild] الخاصة به (الأسطر 23-28)؛

في المحاولة الثالثة، السمة [$attributeOfChild] لها أيضًا نطاق [protected]:


<?php

class SomeParent {
  // السمة
  protected $attributeOfParent = 4;

  // طريقة
  public function doTest(): void {
    // من يستدعيها؟
    print "parent :\n";
    var_dump($this);
    // العرض الأصلي
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }

}

class SomeChild extends SomeParent {
  // السمة
  protected $attributeOfChild = 14;

  // طريقة
  public function doTest(): void {
    // عرض التابع
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";

    // الأصل
    parent::doTest();
  }

}

// النص البرمجي الرئيسي
print "---------------------------test1\n";
(new SomeParent())->doTest();
print "---------------------------test2\n";
(new SomeChild())->doTest();

فيما يلي نتائج التنفيذ:

---------------------------test1
parent :
object(SomeParent)#1 (1) {
  ["attributeOfParent":protected]=>
  int(4)
}
parent : attributeOfParent=4

Notice: Undefined property: SomeParent::$attributeOfChild in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-21.php on line 14
parent : attributeOfChild=
---------------------------test2
child : attributeOfParent=4
child : attributeOfChild=14
parent :
object(SomeChild)#1 (2) {
  ["attributeOfChild":protected]=>
  int(14)
  ["attributeOfParent":protected]=>
  int(4)
}
parent : attributeOfParent=4
parent : attributeOfChild=14
  • السطر 22: هذه المرة، داخل العنصر الأصلي، [$this] من النوع [SomeChild] (الأسطر 15-20) يرى السمة المحمية [$attributeOfChild] الخاصة بفئته [SomeChild].

ما الذي يمكن استخلاصه من هذه الاختبارات؟

  • أن [$this]، وهي مثيل لفئة أصلية تُستخدم في إحدى طرق الفئة الأصلية، ترى:
    • سمات وطرق الفئة الأم بغض النظر عن مستوى ظهورها؛
    • لا ترى أي شيء من سمات وأساليب فئاتها الفرعية؛

وهذا هو السلوك المتوقع.

  • أن مثيل [$this] لفئة فرعية، المستخدم في إحدى طرق الفئة الفرعية، يرى:
    • سمات وطرق الفئة الأم إذا كانت رؤيتها على الأقل [protected]. أما تلك التي تكون رؤيتها [private] فلا تُرى؛
    • سمات وأساليب الفئة الفرعية بغض النظر عن مستوى الرؤية الخاص بها؛

هذا هو السلوك المتوقع.

  • أن مثيل [$this] لفئة فرعية، المستخدم في إحدى طرق الفئة الأصلية، يرى:
    • سمات وأساليب الفئة الأم بغض النظر عن مستوى الرؤية؛
    • السمات والطرق الخاصة بفئتها فقط إذا كانت رؤيتها [protected] على الأقل. أما تلك التي تكون رؤيتها [private] فلا تظهر؛

وهذا سلوك غير متوقع.

5.19. ترميز فئة بـ jSON

غالبًا ما توجد الطريقة [__toString] في الفئة: ومن المفترض أن تُرجع سلسلة أحرف تمثل الكائن الذي يستدعيها. قد يكون من المغري أن تكون هذه السلسلة سلسلة jSON. سنستكشف هذا المسار الآن.

Image

سنستخدم الفئة [Personne] التالية:


<?php

class Personne {
  // السمات
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;

  // مُعيّن عام
  public function setFromArray(array $arrayOfAttributes): Personne {
    // تهيئة بعض سمات الفئة
    foreach ($arrayOfAttributes as $attribute => $value) {
      $this->$attribute = $value;
    }
    // إرجاع الكائن
    return $this;
  }

  // مُستردات
  public function getNom() {
    return $this->nom;
  }

  public function getPrénom() {
    return $this->prénom;
  }

  public function getÂge() {
    return $this->âge;
  }

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

  // __toString
  public function __toString(): string {
    // تحديد الكائن
    var_dump($this);
    // استرداد سماته
    $attributes = \get_object_vars($this);
    var_dump($attributes);
    // نقوم بإرجاع سلسلة jSON من السمات
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }

}

تعليقات

  • الأسطر 5-8: السمات الأربع للفئة؛
  • الأسطر 20-35: دالات الحصول التي تسمح بالحصول على قيمة هذه السمات؛
  • الأسطر 11-18: دالة تعيين عامة تسمح بتهيئة السمات من مصفوفة مرتبطة [$arrayOfAttributes] ذات مفاتيح مطابقة لسمات الفئة؛
  • الأسطر 38-46: طريقة [__toString] للفئة؛
  • السطر 42: تسمح الدالة PHP [get_object_vars] بالحصول على قيم سمات الفئة في شكل مصفوفة مرتبطة [‘الاسم’=>’الاسم1’، ‘الاسم الأول’=>’الاسم الأول1’، ‘العمر’=>العمر1، ‘الأطفال’=>[]
  • السطر 45: يتم إرجاع السلسلة jSON من هذا المصفوفة من السمات؛

دعونا ندرس البرنامج النصي [json-01.php] الذي يستخدم الفئة [Personne]:


<?php

// فئة «شخص»
require "Personne.php";

// إنشاء مثيل للأب
$père = new Personne();
// تهيئة الوالد
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// إنشاء مثيل وتهيئة الطفل1
$enfant1 = (new Personne())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 17
  ]);
// إنشاء مثيل «الابن 2» وتهيئته
$enfant2 = (new Personne())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// تهيئة الأبناء
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);

// عرض عناصر الكائن الأب
$enfant1=($père->getEnfants())[0];
$enfant2=($père->getEnfants())[1];
print "------------------------enfant1\n";
print "enfant1=$enfant1\n";
print "------------------------enfant2\n";
print "enfant2=$enfant2\n";
print "------------------------père\n";
print "père=$père\n";

تعليقات

  • الأسطر 6-13: يتم تهيئة كائن [Personne] [$père] باستخدام الطريقة [Personne::setFromArray] التي تسمحتهيئة كائن [Personne] باستخدام مصفوفة تحتوي على مفاتيح مطابقة لسمات الفئة [Personne]؛
  • الأسطر 14-19: يتم تهيئة كائن [Personne] [$enfant1] بنفس الطريقة؛
  • الأسطر 21-25: يتم تهيئة كائن [Personne] [$enfant2]؛
  • الأسطر 27-29: يتم تهيئة السمة [$père→enfants] بمصفوفة تضم الطفلين؛
  • الأسطر 32-33: يتم تعيين العنصرين الفرعيين إلى العنصر الأصلي؛
  • السطر 35: ستعمل العملية [print] على تحويل الكائن [$enfant1] إلى سلسلة أحرف. ولهذا الغرض، تستخدم هذه العملية الطريقة [__toString] الخاصة بالكائن. لذا نتوقع أن نرى السلسلة jSON الخاصة بالكائن؛
  • السطران 38-39: نفعل الشيء نفسه مع الكائن الأب؛

والنتائج هي كما يلي:


------------------------enfant1
object(Personne)#2 (4) {
  ["nom":"Personne":private]=>
  string(11) "Bertholomé"
  ["prénom":"Personne":private]=>
  string(7) "Sylvain"
  ["âge":"Personne":private]=>
  int(17)
  ["enfants":"Personne":private]=>
  NULL
}
array(4) {
  ["nom"]=>
  string(11) "Bertholomé"
  ["prénom"]=>
  string(7) "Sylvain"
  ["âge"]=>
  int(17)
  ["enfants"]=>
  NULL
}
enfant1={"nom":"Bertholomé","prénom":"Sylvain","âge":17,"enfants":null}
------------------------enfant2
object(Personne)#3 (4) {
  ["nom":"Personne":private]=>
  string(11) "Bertholomé"
  ["prénom":"Personne":private]=>
  string(10) "Géraldine"
  ["âge":"Personne":private]=>
  int(12)
  ["enfants":"Personne":private]=>
  NULL
}
array(4) {
  ["nom"]=>
  string(11) "Bertholomé"
  ["prénom"]=>
  string(10) "Géraldine"
  ["âge"]=>
  int(12)
  ["enfants"]=>
  NULL
}
enfant2={"nom":"Bertholomé","prénom":"Géraldine","âge":12,"enfants":null}
------------------------père
object(Personne)#1 (4) {
  ["nom":"Personne":private]=>
  string(11) "Bertholomé"
  ["prénom":"Personne":private]=>
  string(10) "Dieudonné"
  ["âge":"Personne":private]=>
  int(58)
  ["enfants":"Personne":private]=>
  array(2) {
    [0]=>
    object(Personne)#2 (4) {
      ["nom":"Personne":private]=>
      string(11) "Bertholomé"
      ["prénom":"Personne":private]=>
      string(7) "Sylvain"
      ["âge":"Personne":private]=>
      int(17)
      ["enfants":"Personne":private]=>
      NULL
    }
    [1]=>
    object(Personne)#3 (4) {
      ["nom":"Personne":private]=>
      string(11) "Bertholomé"
      ["prénom":"Personne":private]=>
      string(10) "Géraldine"
      ["âge":"Personne":private]=>
      int(12)
      ["enfants":"Personne":private]=>
      NULL
    }
  }
}
array(4) {
  ["nom"]=>
  string(11) "Bertholomé"
  ["prénom"]=>
  string(10) "Dieudonné"
  ["âge"]=>
  int(58)
  ["enfants"]=>
  array(2) {
    [0]=>
    object(Personne)#2 (4) {
      ["nom":"Personne":private]=>
      string(11) "Bertholomé"
      ["prénom":"Personne":private]=>
      string(7) "Sylvain"
      ["âge":"Personne":private]=>
      int(17)
      ["enfants":"Personne":private]=>
      NULL
    }
    [1]=>
    object(Personne)#3 (4) {
      ["nom":"Personne":private]=>
      string(11) "Bertholomé"
      ["prénom":"Personne":private]=>
      string(10) "Géraldine"
      ["âge":"Personne":private]=>
      int(12)
      ["enfants":"Personne":private]=>
      NULL
    }
  }
}
père={"nom":"Bertholomé","prénom":"Dieudonné","âge":58,"enfants":[{},{}]}

تعليقات

  • السطور 2-11: الكائن [$enfant1]؛
  • السطور 12-21: جدول سمات الكائن [$enfant1]. جميعها موجودة بالفعل؛
  • السطر 22: لدينا بالفعل السلسلة jSON الخاصة بالكائن [$enfant1]؛
  • الأسطر 23-44: الأمر نفسه بالنسبة للكائن [$enfant2]؛
  • الأسطر 45-112: الأمر يختلف قليلاً بالنسبة للأب، حيث إن سمة [enfants] الخاصة به ليست NULL كما كانت الحال مع الأبناء؛
  • السطر 112: نلاحظ أن الأطفال غير موجودين في السلسلة jSON الخاصة بالأب؛
  • الأسطر 79-111: نلاحظ أنه في جدول سمات الأب، ظل الطفل 1 (الأسطر 89-98) كائنًا، وكذلك الحال بالنسبة للطفل 2 (الأسطر 99-110). ببساطة، التعبير [\get_object_vars($this)] حيث يمثل [$this] الأب ليس تعبيرًا متكررًا: إذا كان أحد سمات الفئة [Personne] كائنًا بحد ذاته، فإن التعبير [\get_object_vars($this)] لا يحاول الحصول على مصفوفة سماته؛

يمكننا تحسين الأمر. نقوم بتعديل الفئة [Personne] لتصبح الفئة [Personne2] التالية:


<?php

class Personne2 {
  // السمات
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;

  // مُعيّن عام
  public function setFromArray(array $arrayOfAttributes): Personne2 {

    // إرجاع الكائن
    return $this;
  }

  // مُستردات
  public function getNom() {
    return $this->nom;
  }



  // __toString
  public function __toString(): string {
    // استرداد سمات الكائن
    $attributes = $this->getAttributes($this);
    $enfants = $attributes["enfants"];
    if ($enfants != NULL) {
      $attributes["enfants"] = [$enfants[0]->getAttributes(), $enfants[1]->getAttributes()];
    }
    // يتم عرض السلسلة JSON للسمات
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }

  public function getAttributes(): array {
    return \get_object_vars($this);
  }

}

تعليقات

  • الأسطر 36-38: تعرض الدالة [getAttributes] مصفوفة سمات الكائن الذي يستدعيها؛
  • الأسطر 25-34: الدالة [__toString]؛
  • السطر 27: يتم استرداد سمات الفئة [Personne] في المصفوفة [$attributes]؛
  • السطر 28: نعلم، استنادًا إلى المثال السابق، أن [$attributes["enfants"]] هو مصفوفة مكونة من كائنين من النوع [Personne]؛
  • الأسطر 29-31: يتم استبدال الكائنين بمصفوفة سماتهما؛
  • السطر 33: لم يتبقَ سوى ترميز مصفوفة السمات التي تم إنشاؤها إلى jSON؛

يستخدم البرنامج النصي [json-02.php] الفئة [Personne2] بالطريقة التالية:


<?php

// فئة Personne2
require "Personne2.php";

// إنشاء مثيل للوالد
$père = new Personne2();
// التهيئة
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// إنشاء مثيل وتهيئة الطفل1
$enfant1 = (new Personne2())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 17
  ]);
// إنشاء مثيل وتهيئة الطفل2
$enfant2 = (new Personne2())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// تهيئة أبناء الكائن الأب
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);

// عرض الكائن الأب
print "------------------------père\n";
print "père=$père\n";

النص البرمجي [json-02.php] مطابق للنص البرمجي [json-01.php] باستثناء أن الفئة [Personne2] حلت محل الفئة [Personne].

نتائج التنفيذ هي كما يلي:

------------------------père
père={"nom":"Bertholomé","prénom":"Dieudonné","âge":58,"enfants":[{"nom":"Bertholomé","prénom":"Sylvain","âge":17,"enfants":null},{"nom":"Bertholomé","prénom":"Géraldine","âge":12,"enfants":null}]}

هذه المرة، تم الحصول على الأبناء مع الأب بشكل صحيح.

الحل السابق غير مرضٍ لأن الأبناء قد يكون لهم أبناء بدورهم. وبذلك نعود إلى المشكلة السابقة.

تحل الفئة [Personne3] هذه المشكلة بالطريقة التالية:


<?php

class Personne3 {
  // السمات
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;

  // مُعيّن عام
  public function setFromArray(array $arrayOfAttributes): Personne3 {

  }

  // مُستردات


  // __toString
  public function __toString(): string {
    // يُرجع سلسلة JSON من السمات
    $attributes = [];
    $this->getRecursiveAttributes($attributes, $this, []);
    // السلسلة JSON من السمات
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }

  public function getAttributes(): array {
    return \get_object_vars($this);
  }

  private function getRecursiveAttributes(array &$attributes, $value, $keys): void {
    // تحليل القيمة [$value]
    // $keys عبارة عن مصفوفة [key1, key2, .., keyn]
    // $value=$attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX….[keyn]
    // إذا كان [$value] كائنًا، فإننا نستخدم طريقة [getAttributes] الخاصة به
    if (\is_object($value)) {
      // سمات الكائن [$value]
      $objectAttributes = $value->getAttributes();
      // ماذا نفعل بالنتيجة؟
      if ($keys) {
        // في [$attributes]، سنستبدل $value بالجدول الخاص بسماته
        // يجب إنشاء العنصر $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[keyn]
        // حيث $keys هو المصفوفة [key1, key2, .., keyn]
        // يتم أخذ مرجع المصفوفة [$attributes]
        $attribute = &$attributes;
        // نقوم بمسح جدول المفاتيح
        foreach ($keys as $key) {
          // نأخذ مرجع السمة
          $attribute = &$attribute[$key];
        }
        // هنا $attribut و $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[key(n)] متطابقة
        // وهي تشترك في نفس موقع الذاكرة
        // يتم استبدال الكائن [$value] بمصفوفة سماته؛
        // يجب كتابة $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[keyn]=$objectAttributes
        // وهو ما يعادل $attribute = $objectAttributes
        $attribute = $objectAttributes;
      } else {
        // لا توجد مفاتيح - نحن في بداية استكشاف الكائن
        // $objectAttributes يمثل سمات المستوى الأول للفئة
        $attributes += $objectAttributes;
      }
      // ربما لا تزال هناك كائنات في [$objectAttributes]
      // نستكشف سمات [$objectAttributes]
      $this->getRecursiveAttributes($attributes, $objectAttributes, $keys);
    } else {
      if (\is_array($value)) {
        // لدينا مصفوفة - نقوم بتحليل كل عنصر من عناصرها
        foreach ($value as $key => $élément) {
          // نضيف المفتاح الحالي إلى المصفوفة $keys
          \array_push($keys, $key);
          // نقوم بتحليل $élément
          $this->getRecursiveAttributes($attributes, $élément, $keys);
          // نحذف المفتاح الذي تم تحليله للتو من المصفوفة $keys
          \array_pop($keys);
        }
      }
    }
  }

تعليقات

  • السطور 21-22: هذه المرة، تطلب الطريقة [__toString] سمات فئتها وتطلب أن يتم ذلك بشكل متكرر: إذا كانت السمة كائنًا أو مصفوفة من الكائنات، فيجب استبدال كل كائن بمصفوفة سماته في مصفوفة السمات النهائية للفئة؛
  • الأسطر 31-78: تقوم الدالة [getRecursiveAttributes] بهذه المهمة. وقد تم توضيح شفرة هذه الدالة بالتعليقات. غالبًا ما يكون كتابة دالة متكررة أمرًا معقدًا. وهذا هو الحال هنا. ولن يفقد القارئ شيئًا إذا لم يفهمها. فهناك مكتبات تقوم بهذه المهمة. ويحدث الاستدعاء المتكرر في السطرين 64 و72؛
  • وتكمن أهمية هذا الكود في أنه لم يُكتب لفئة [Personne3] وحدها. فهو صالح لأي فئة تحتوي على سمات ذات قيم من أنواع كائنات مختلفة، طالما أن الفئات المستخدمة من قبل الفئة الرئيسية تحتوي، مثلها، على الطريقة [getAttributes] الموجودة في الأسطر 27-29

يستخدم البرنامج النصي [json-03.php] الفئة [Personne3] بالطريقة التالية:


<?php

// فئة Personne3
require "Personne3.php";

// إنشاء مثيل للوالد
$père = new Personne3();
// التهيئة
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// إنشاء مثيل وتهيئة الطفل1
$enfant1 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 27
  ]);
// إنشاء مثيل «الابن2» وتهيئته
$enfant2 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// تهيئة أبناء الأب
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);
// إنشاء مثيل وتهيئة الطفل 11
$enfant11 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Gaëtan",
  "âge" => 2
  ]);
// إنشاء مثيل وتهيئة الطفل 12
$enfant12 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Mathilde",
  "âge" => 1
  ]);
// تهيئة أبناء «الطفل 1»
$enfant1->setFromArray([
  "enfants" => [$enfant11, $enfant12]
]);
// عرض العنصر الأصلي
print "------------------------père\n";
print "père=$père\n";
  • الأسطر 30-45: يُعطى لـ [$enfant1] طفلان؛

نتائج التنفيذ هي كما يلي:

------------------------père
père={"nom":"Bertholomé","prénom":"Dieudonné","âge":58,"enfants":[{"nom":"Bertholomé","prénom":"Sylvain","âge":27,"enfants":[{"nom":"Bertholomé","prénom":"Gaëtan","âge":2,"enfants":null},{"nom":"Bertholomé","prénom":"Mathilde","âge":1,"enfants":null}]},{"nom":"Bertholomé","prénom":"Géraldine","âge":12,"enfants":null}]}

وإذا قمنا بتنسيق هذه النتيجة، نحصل على ما يلي:


père={
    "nom": "Bertholomé",
    "prénom": "Dieudonné",
    "âge": 58,
    "enfants": [
        {
            "nom": "Bertholomé",
            "prénom": "Sylvain",
            "âge": 27,
            "enfants": [
                {
                    "nom": "Bertholomé",
                    "prénom": "Gaëtan",
                    "âge": 2,
                    "enfants": null
                },
                {
                    "nom": "Bertholomé",
                    "prénom": "Mathilde",
                    "âge": 1,
                    "enfants": null
                }
            ]
        },
        {
            "nom": "Bertholomé",
            "prénom": "Géraldine",
            "âge": 12,
            "enfants": null
        }
    ]
}

لقد تم استرداد السلسلة jSON من جميع الكائنات [Personne] التي تشكل الكائن الأب.