Skip to content

5. الفئات

المفردات: الفئة هي نوع في PHP. يُطلق على المتغير من هذا النوع اسم كائن. الكائن هو مثيل (instance) لفئة.

5.1. التسلسل الهرمي للبرامج النصية

Image

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

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


<?php
 
// a generic object
// $obj1=new stdClass();
// any variable can have attributes by construction
$obj1->attr1 = "un";
$obj1->attr2 = 100;
// displays the
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// modify object
$obj1->attr2 += 100;
// displays the
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// copies the value of object1 (address of the object pointed to) to object2
// the two variables are then different but point to the same object
$obj2 = $obj1;
// modify obj2
$obj2->attr2 = 0;
// displays both objects
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
// changes the object pointed to by obj1
$obj1 = new stdClass();
print "obj1 :\n";
print_r($obj1);
print "obj2 :\n";
print_r($obj2);
// assigns the reference (address) from object2 to object3
// $obj2 and $obj3 are then one and the same variable
$obj3 = &$obj2;
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);
// modify obj3
$obj3->attr2 = 10;
// displays both objects
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
print "objet3=[$obj3->attr1,$obj3->attr2]\n";
// changes the object pointed to by obj2
$obj2 = new stdClass();
$obj2->attr3 = "deux";
$obj2->attr4 = 20;
// displays the two objects $obj2 and $obj3
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);
 
// is an object a dictionary?
print count($obj3) . "\n";
while (list($attribut, $valeur) = each($obj3)) {
  print "obj3[$attribut]=$valeur\n";
}
// end
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 أدناه]. يمكننا القول أن المتغيرين هما أسماء مستعارة لنفس موقع الذاكرة. كلاهما يشير إلى كائن، الكائن A أدناه [2]؛
    • تؤدي العملية $obj2=new stdClass() إلى إنشاء كائن جديد، وهو الكائن B [3 أدناه]، ويتم تعيين عنوان هذا الكائن الجديد للمتغير $obj2. وبما أن $obj2 و$obj3 هما اسمان بديلان لنفس موقع الذاكرة، فإن $obj3 يشير أيضًا إلى الكائن الجديد، الكائن B. ويظهر ذلك في الأسطر 16–27 و30–41 من النتائج؛

Image

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

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

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


<?php
 
class Personne {
 
  // class attributes
  // undeclared - can be created dynamically
  // method
  function identite() {
    // a priori, uses non-existent attributes
    return "[$this->prenom,$this->nom,$this->age]";
  }
 
}
 
// test
// attributes are public and can be created dynamically
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// method call
print "personne=" . $p->identite() . "\n";
// end
exit;

النتائج:

personne=[Paul,Langevin,48]

تعليقات

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

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

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


<?php
 
class Personne {
 
  // class attributes
  var $prenom;
  var $nom;
  var $age;
 
  // method
  function identite() {
    return "[$this->prenom,$this->nom,$this->age]";
  }
 
}
 
// test
// attributes are public
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// method call
print "personne=" . $p->identite() . "\n";
// end
exit;

النتائج:

personne=[Paul,Langevin,48]

تعليقات

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

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

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


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
class Personne {
// class attributes
  private $prenom;
  private $nom;
  private $age;
 
// getters and setters
  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;
  }
 
// manufacturer
  public function __construct(string $prenom, string $nom, int $age) {
    // we go through sets
    $this->setPrenom($prenom);
    $this->setNom($nom);
    $this->setAge($age);
  }
 
// method toString
  public function __toString(): string {
    return "[$this->prenom,$this->nom,$this->age]";
  }
 
}
 
// test
// creation of a Personne object
$p = new Personne("Paul", "Langevin", 48);
// identity of this person
print "personne=$p\n";
// we change the age
$p->setAge(14);
// identity of the person
print "personne=$p\n";
// end
exit;

النتائج:

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

تعليقات

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

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

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

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

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

5.6. فئة Person مع فحوصات الصحة في المنشئ

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

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


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace;
namespace Exemples;
 
// person class
class Personne {
// class attributes
  private $prenom;
  private $nom;
  private $age;
 
// getters and setters
  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 {
    // first name must be non-empty
    $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 {
    // name must be non-empty
    $nom = trim($nom);
    if ($nom === "") {
      throw new \Exception("Le nom doit être non vide");
    } else {
      $this->nom = $nom;
    }
  }
 
  public function setAge(int $âge): void {
    // age must be valid
    if ($âge < 0) {
      throw new \Exception("L'âge doit être un entier positif ou nul");
    } else {
      $this->age = $âge;
    }
  }
 
// manufacturer
  public function __construct(string $prenom, string $nom, int $age) {
    // we go through sets
    $this->setPrenom($prenom);
    $this->setNom($nom);
    $this->setAge($age);
  }
 
  // method
  public function initWithPersonne(Personne $p): void {
    // initializes the current object with a person $p
    $this->__construct($p->prenom, $p->nom, $p->age);
  }
 
  // method toString
  function __toString(): string {
    return "[$this->prenom,$this->nom,$this->age]";
  }
 
}

تعليقات

  • السطر 4: يحدد أنه يجب مراعاة نوع معلمات الدالة عند الإعلان عنها؛
  • السطر 7: يحدد مساحة اسم. يصبح الاسم الكامل (أو المؤهل) لفئة Person هو \Examples\Person. لاحظ الحرف \ في بداية الاسم المؤهل: يؤدي ذلك إلى اسم مؤهل مطلق. إذا غاب هذا الحرف، يكون الاسم نسبيًا (نسبةً إلى مساحة الاسم الحالية). وبالتالي، إذا كانت الفئتان A و B جزءًا من نفس مساحة الاسم E، في كود الفئة A، يمكن الوصول إلى الفئة B باستخدام الترميز النسبي B. إذا كانت الفئة A جزءًا من مساحة الاسم E1 وكانت B جزءًا من مساحة الاسم E2، في كود A، سيتم الوصول إلى B باستخدام الترميز المطلق \E2\B. تحديد فئة داخل مساحة اسم ليس إلزاميًا، لكن NetBeans يصدر تحذيرًا إذا لم تقم بذلك. لذلك، سنقوم بذلك. علاوة على ذلك، يجب أن تتوافق مساحات الأسماء مع بنية دليل الملفات. وبالتالي، يجب أن تكون الفئة A في مساحة الاسم E1 في ملف باسم E1/A.php. هذا ليس إلزاميًا، لكن مرة أخرى، يصدر NetBeans تحذيرًا إذا لم تقم بذلك. في مثال الفئة [\Exemples\Personne]، يصدر NetBeans تحذيرًا لأن مسار ملف [Personne.php] هو [exemples/classes/Personne.php] وبالتالي لا يتطابق مع مساحة الاسم. لا تخلط بين مسار الملف ومساحة الاسم. يستخدم الاسم الكامل للفئة مساحة اسم ولا علاقة له بمسار ملف PHP الخاص بالفئة. العلاقة بين مسار الملف ومساحة الاسم اختيارية ويمكن تجاهلها، كما فعلنا هنا؛
  • الأسطر 12-14: السمات الخاصة الثلاث للفئة؛
  • الأسطر 29–37: تهيئة السمة first_name والتحقق من قيمة التهيئة؛
  • السطر 31: تزيل الدالة trim($string) المسافات في بداية ونهاية $string. وبالتالي، فإن trim("abcd") هي السلسلة "abcd" و trim(" ") هي السلسلة الفارغة؛
  • السطر 32: إذا كان الاسم الأول فارغًا، يتم إلقاء استثناء (السطر 33)؛ وإلا، يتم تخزين الاسم الأول (السطر 35). لإلقاء استثناء، استخدمنا هنا الفئة المحددة مسبقًا [Exception]. هنا، يتعين علينا استخدام اسمها المطلق [\Exception]. إذا استخدمنا اسمها النسبي [Exceptionفسيتم البحث عن هذه الفئة في مساحة الاسم الحالية، أي مساحة الاسم [Examples] لفئة Person. وبالتالي، سيبحث مترجم PHP عن فئة بالاسم المطلق [\Examples\Exceptionوهي غير موجودة؛

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


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// including definition of the Person class
require_once __DIR__."/Personne.php";
 
// qualified name of the Person class
use \Exemples\Personne;
 
// test
// creation of a Personne object
$p = new Personne("Paul", "Langevin", 48);
// identity of this person
print "Exemple1, personne=$p\n";
// creation of an erroneous Person object
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";
}
// creation of an erroneous Person object
try {
  $p = new Personne("", "yy", 10);
} catch (\Exception $e1) {
  print "Exemple3, erreur : " . $e1->getMessage() . "\n";
} catch (\TypeError $e2) {
  print "Exemple3, erreur : " . $e2->getMessage() . "\n";
}
 
// end
exit;

التعليقات

  • السطر 7: سيستخدم البرنامج النصي فئة [Person]. لذلك يجب أن نخبر مترجم PHP أين يمكنه العثور على تعريف هذه الفئة. وهذا هو دور عبارات [include file] و [require file]. وقد استخدمنا هنا عبارة [include]. الفرق بين العبارتين هو كما يلي: إذا واجهت عبارة [include file] أخطاء أثناء تحميل [file]، يتم إصدار خطأ [E_WARNING] ولكن التنفيذ يستمر، في حين أن [require] في نفس الحالة تولد خطأ فادحًا ويتوقف تنفيذ البرنامج النصي. لكل من هاتين العبارتين متغير: [include_once] و [require_once]. يتيح لك هذان النوعان التعامل مع الحالات التي يتم فيها تضمين نفس الملف عدة مرات. تخيل مشروعًا يتكون من عدة نصوص PHP، بعضها يشير إلى فئة [Person]. سيؤدي تنفيذها إلى تضمين ملف [Person.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: نستخدم فئة [Person] التي حددناها للتو. لا يحتوي البرنامج النصي [classes-05.php] على مساحات أسماء. يستخدم السطر 14 الاسم النسبي لفئة [Person] بدون مساحة اسم. وبما أن فئة [Person] لا تحتوي على مساحة اسم، يتم البحث عنها داخل البرنامج النصي [classes-05.php] نفسه، وبالتالي لن يتم العثور عليها. هناك حلان لهذه المشكلة:
    • استخدام الاسم الكامل للفئة [\Examples\Person]؛
    • استخدام عبارة `use` في السطر 10. يشير هذا إلى أن الكود التالي يستخدم الفئة `[Examples\Person]`؛
  • السطر 10: يُعلم الأمر use المُترجم أن فئة [Person] المشار إليها في السطر 14 هي في الواقع فئة [\Examples\Person]. ومع ذلك، أين سيجد المُترجم كود هذه الفئة؟ السطر 7 يخبره بذلك. يشير هذا السطر إلى أنه لتنفيذ البرنامج النصي الحالي، يجب أيضًا تحميل البرنامج النصي [Person.php]. هنا، يتم استخدام اسم الملف النسبي. وبالتالي، سيتم البحث عنه في المجلد الذي يحتوي على البرنامج النصي [classes-05.php]. لذلك، يجب أن يكون البرنامجان النصيان [Person.php] و [classes-05.php] في نفس المجلد. وهذا هو الحال هنا، حيث يوجد كلاهما في المجلد [examples/classes]. العبارة الموجودة في السطر 10 تعادل:

use \Exemples\Personne as Personne;

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

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

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

لذلك، يجب أن يكون المعامل [age] الذي يتم تمريره إلى المنشئ من النوع integer. إذا لم يكن الأمر كذلك، فإن مترجم PHP يرمي [TypeError]. علاوة على ذلك، ترمي طرق [set] لفئة [Person] استثناء [\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]. في طريقة [Person.setFirstName]، كتبنا:

  public function setPrenom(string $prénom) {
    // le prénom doit être non vide
    $prénom = trim($prénom);
    if ($prénom === "") {
      throw new \Exception("Le prénom doit être non vide");
    } else {
      $this->prenom = $prénom;
    }
}

في السطر 5، يتم إلقاء استثناء مع رسالة الخطأ [يجب ألا يكون الاسم الأول فارغًا]. وهذا ما ستسترده طريقة [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، لا يمكن وجود عدة منشئات بمعلمات مختلفة تسمح بإنشاء كائن بطرق متنوعة. لذلك يمكننا استخدام طرق تعمل كمنشئات:


  // méthode
  public function initWithPersonne(Personne $p) {
    // initialise l'objet courant avec une personne $p
    $this->__construct($p->prenom, $p->nom, $p->age);
}

تعليقات

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

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


<?php
 
// including definition of the Person class
require_once __DIR__."/Personne.php";
// declaration of the qualified name of the Personne class
use \Exemples\Personne;
 
// test
// creation of a Personne object
try {
  $p = new Personne("Paul", "Langevin", 48);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
// identity of this person
print "personne=$p\n";
// creation of a second person
try {
  $p2 = new Personne("Laure", "Adeline", 67);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
// initialization of the first with the values of the second
try {
  $p->initWithPersonne($p2);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
 
// check
print "personne=$p\n";
// end
exit;
  • الأسطر 14 و23 و30: من الشائع أنه بعد حدوث استثناء، يجب إيقاف تنفيذ البرنامج النصي الخاص بوحدة التحكم إذا كان الخطأ الذي تم مواجهته غير قابل للإصلاح. لكن الأمر يختلف في البرامج النصية الخاصة بالويب: فنحن لا نوقف تنفيذ البرنامج النصي بل نعرض صفحة خطأ. إذا كنا داخل دالة، فإننا لا نستخدم عبارة exit بل نستخدم return: لا نوقف تنفيذ البرنامج النصي (exit) بل نخرج من الدالة (return) بعد تعيين خطأ؛

النتائج:

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

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

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


<?php
 
require_once __DIR__."/Personne.php";
use \Exemples\Personne;
 
// test
// create an array of Personne objects
// to make the code easier to understand, we don't handle the possible exception
$groupe = [new Personne("Paul", "Langevin", 48), new Personne("Sylvie", "Lefur", 70)];
 
// identity of these persons
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
 
// end
exit;

النتائج:

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

تعليقات

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

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

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


<?php
 
// strict adherence to declared function parameter types
declare (strict_types=1);
 
// namespace
namespace Exemples;
 
// a class derived from personne
class Enseignant extends Personne {
  // attributes
  private $discipline;   // subject taught
 
  // getter and setter
 
  public function getDiscipline(): string {
    return $this->discipline;
  }
 
  public function setDiscipline(string $discipline): void {
    $this->discipline = $discipline;
  }
 
  // manufacturer
  public function __construct(string $prénom, string $nom, int $âge, string $discipline) {
    // parent attributes
    parent::__construct($prénom, $nom, $âge);
    // other attributes
    $this->setDiscipline($discipline);
  }
 
  // overloading the __toString function of the parent class
  public function __toString(): string {
    return "[" . parent::__toString() . ",$this->discipline]";
  }
 
}

تعليقات

  • السطر 7: فئة [Teacher] هي أيضًا جزء من مساحة الاسم [Examples]؛
  • السطر 10: فئة Teacher تمتد من فئة Person. فئة Teacher المشتقة ترث سمات وأساليب فئتها الأم؛
  • السطر 12: تضيف فئة Teacher سمة جديدة، وهي subject، وهي سمة خاصة بها؛
  • السطر 25: يأخذ منشئ فئة Teacher 4 معلمات:
    • 3 لتهيئة فئتها الأصلية (first_name، last_name، age)، السطر 27؛
    • 1 لتهيئة نفسها (subject)، السطر 29؛
  • السطر 27: الفئة المشتقة لديها حق الوصول إلى طرق ومنشئات فئتها الأصلية عبر الكلمة الرئيسية parent::. هنا، نمرر المعلمات (first_name، last_name، age) إلى منشئ الفئة الأصلية؛
  • الأسطر 33–35: تستخدم طريقة __toString للفئة المشتقة طريقة __toString للفئة الأصلية؛

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


<?php
 
// inclusion of the definition of the two classes
require_once __DIR__."/Personne.php";
require_once __DIR__."/Enseignant.php";
// declaration of the two classes used
use \Exemples\Personne;
use \Exemples\Enseignant;
 
// test
// creation of an array of Personne and derived objects
// for the simplicity of the example, we don't handle exceptions
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70));
 
// identity of these persons
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
// end
exit;

تعليقات

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

النتائج:

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

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

يُنشئ المثال التالي فئة Student مشتقة من فئة Person في ملف [Student.php]:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Exemples;
 
class Etudiant extends Personne {
  // attributes
  private $formation;   // training pursued
 
  // getter and setter
  public function getFormation(): string {
    return $this->formation;
  }
 
  public function setFormation(string $formation): void {
    $this->formation = $formation;
  }
 
  // manufacturer
  public function __construct(string $prénom, string $nom, int $âge, string $formation) {
    // parent attributes
    parent::__construct($prénom, $nom, $âge);
    // other attributes
    $this->setFormation($formation);
  }
 
  // overloading the __toString function of the parent class
  public function __toString(): string {
    return "[" . parent::__toString() . ",$this->formation]]";
  }
 
}

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


<?php
 
// inclusion and definition of classes used by the script
require_once __DIR__."/Personne.php";
use \Exemples\Personne;
require_once __DIR__."/Enseignant.php";
use \Exemples\Enseignant;
require_once __DIR__."/Etudiant.php";
use \Exemples\Etudiant;
 
// test
// creation of a table of person objects and derivatives
// to make the example easier to understand, we don't handle exceptions
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70), new Etudiant("Steve", "Boer", 23, "iup2 qualité"));
 
// identity of these persons
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
// end
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 {
 
  // manufacturer
  public function __construct() {
    print "constructeur de la classe Classe1\n";
  }
 
}
 
class Classe2 extends Classe1 {

  // manufacturer
  public function __construct() {
    // the constructor of the parent class is not called implicitly
    print "constructeur de la classe Classe2\n";
  }
 
}
 
class Classe3 extends Classe1 {
 
  // manufacturer
  public function __construct() {
    // explicit call to parent class constructor
    parent::__construct();
    // code specific to Classe3
    print "constructeur de la classe Classe3\n";
  }
 
}
 
// tests
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] الخاصة بفئة [Person] (انظر الرابط) في الفئتين الفرعيتين [Teacher] (انظر الرابط) و[Student] (انظر الرابط). ويوضح البرنامج النصي [classes-13.php] هذا المفهوم مرة أخرى:


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// main class
class Classe1 {
 
  public function f(): int {
    return 1;
  }
 
  function g(): int {
    return 2;
  }
 
}
 
// derived class
class Classe2 extends Classe1 {
 
  // redefine the f function of the parent class
  public function f(): int {
    return parent::f() + 10;
  }
 
}
 
// code
$c2 = new Classe2();
print $c2->f() . "\n";
print $c2->g() . "\n";
$c1=new Classe1();
print $c1->f()."\n";

تعليقات

  • الأسطر 7–17: تحدد الفئة [Class1] طريقتين، f و g؛
  • الأسطر 20–27: الفئة [Class2] تمتد من الفئة [Class1] وتعيد تعريف طريقتها f؛

النتائج

1
2
3
11
2
1

تعليقات

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

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

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


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// main class
class Classe1 {
 
  public function f(): int {
    return 1;
  }
 
  function g(): int {
    return 2;
  }
 
}
 
// derived class
class Classe2 extends Classe1 {
 
  // redefine the f function of the parent class
  public function f(): int {
    return parent::f() + 10;
  }
 
}
 
// the function parameter is of type Class1 or derived
function doSomething(Classe1 $c1): void {
  print $c1->f() + $c1->g() . "\n";
}
 
// code
// create a Class2 object derived from Class1
$c2 = new Classe2();
// we call doSomething with
doSomething($c2);

تعليقات

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

النتائج

13

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

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

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

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

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


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// abstract main class
abstract Class Classe1 {
 
  // method known to all derived classes
  public function f(): int {
    return 1;
  }
 
  // abstract g method - will be defined by derived classes
  abstract function g(): int;
}
 
// derived class
Class Classe2 extends Classe1 {
 
  // the g method of the parent class must be defined
  public function g(): int {
    return parent::f() + 10;
  }

}
 
// derived class
Class Classe3 extends Classe1 {
 
  // the g method of the parent class must be defined
  public function g(): int {
    return parent::f() + 20;
  }
 
  // we can redefine the f method of the parent class
  public function f(): int {
    return 2;
  }
 
}
 
// code
$c2 = new Classe2();
print $c2->f() . "\n";
print $c2->g() . "\n";
$c3 = new Classe3();
print $c3->f() . "\n";
print $c3->g() . "\n";

تعليقات

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

النتائج

1
2
3
4
1
11
2
21

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

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


<?php
 
// namespace
namespace Exemples;
 
// non-derivable class
final Class Classe1 {
  
}
 
// derived class
Class Classe2 extends Classe1 {
  
}
 
// code - must cause an error
new Classe2();

تعليقات

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

النتائج

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
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// namespace
namespace Exemples;
 
// main class
Class Classe1 {
 
  // this method cannot be redefined in a derived class
  public final function f(): int {
    return 1;
  }
 
}
 
// derived class
Class Classe2 extends Classe1 {
 
  public function f(): int {
    return 2;
  }
 
}
 
// code - must cause an error
new Classe2();

تعليقات

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

النتائج

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 method
  static function say(string $message): void {
    print "$message\n";
  }
 
}
 
// test -------------------
Classe1::say("hello");

تعليقات

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

النتائج

hello

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


<?php
 
class Classe1 {
  // static attribute
  private static $nbObjects = 0;
 
  public function __construct() {
    print "constructeur Classe1\n";
    self::$nbObjects++;
  }
 
  // static method
  static function say(): void {
    print self::$nbObjects ." objets de type [Classe1] ont été construits\n";
  }
 
}
 
// test -------------------
new Classe1();
new Classe1();
Classe1::say();

تعليقات

  • السطر 5: نعلن سمة ثابتة ستحسب عدد مثيلات الفئة [Class1] التي تم إنشاؤها. هذه ليست سمة يمكن أن تنتمي إلى مثيل من الفئة. في الواقع، إذا تم إنشاء كائنين، 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 {
  // attribute
  private $attributeOfParent = 4;
 
  // method
  public function doTest(): void {
    // who's calling?
    print "parent :\n";
    var_dump($this);
    // parent display
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }
 
}
 
class SomeChild extends SomeParent {
  // attribute
  private $attributeOfChild = 14;
 
  // method
  public function doTest(): void {
    // children's display
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";
 
    // parent
    parent::doTest();
  }
}
 
// main script
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 {
  // attribute
  protected $attributeOfParent = 4;
 
  // method
  public function doTest(): void {
    // who's calling?
    print "parent :\n";
    var_dump($this);
    // parent display
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }
 
}
 
class SomeChild extends SomeParent {
  // attribute
  private $attributeOfChild = 14;
 
  // method
  public function doTest(): void {
    // children's display
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";
 
    // parent
    parent::doTest();
  }
 
}
 
// main script
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 {
  // attribute
  protected $attributeOfParent = 4;
 
  // method
  public function doTest(): void {
    // who's calling?
    print "parent :\n";
    var_dump($this);
    // parent display
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }
 
}
 
class SomeChild extends SomeParent {
  // attribute
  protected $attributeOfChild = 14;
 
  // method
  public function doTest(): void {
    // children's display
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";
 
    // parent
    parent::doTest();
  }
 
}
 
// main script
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

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


<?php
 
class Personne {
  // attributes
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;
 
  // setter global
  public function setFromArray(array $arrayOfAttributes): Personne {
    // initialization of certain class attributes
    foreach ($arrayOfAttributes as $attribute => $value) {
      $this->$attribute = $value;
    }
    // object is returned
    return $this;
  }
 
  // getters
  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 {
    // we identify the object
    var_dump($this);
    // retrieve its attributes
    $attributes = \get_object_vars($this);
    var_dump($attributes);
    // render the jSON attribute string
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }
 
}

تعليقات

  • الأسطر 5–8: السمات الأربع للفئة؛
  • الأسطر 20–35: دالات الاسترجاع التي تسمح لك باسترداد قيم هذه السمات؛
  • الأسطر 11–18: دالة تعيين عامة تقوم بتهيئة الخصائص من مصفوفة مرتبطة [$arrayOfAttributes] تتطابق مفاتيحها مع خصائص الفئة؛
  • الأسطر 38–46: طريقة [__toString] للفئة؛
  • السطر 42: تسترد دالة PHP [get_object_vars] قيم سمات الفئة كمصفوفة ترابطية [‘name’=>’name1’, ‘first_name’=>’first_name1’, ‘age’=>’age1’, ‘children’=>[]
  • السطر 45: نُرجع سلسلة JSON لهذا المصفوف من السمات؛

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


<?php
 
// person class
require "Personne.php";
 
// father instantiation
$père = new Personne();
// father initialization
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// instantiation and initialization child1
$enfant1 = (new Personne())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 17
  ]);
// instantiation and initialization child2
$enfant2 = (new Personne())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// father's children initialization
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);
 
// display father elements
$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: نقوم بتهيئة كائن [Person] [$father] باستخدام طريقة [Person::setFromArray]، والتي تسمح لنا بتهيئة كائن [Person] باستخدام مصفوفة تتطابق مفاتيحها مع سمات فئة [Person]؛
  • الأسطر 14-19: نقوم بتهيئة كائن [Person] [$child1] بنفس الطريقة؛
  • الأسطر 21–25: نقوم بتهيئة كائن [Person] [$child2]؛
  • الأسطر 27–29: يتم تهيئة السمة [$father→children] بمصفوفة من الطفلين؛
  • الأسطر 32-33: نعيّن الطفلين للأب؛
  • السطر 35: تحاول عملية [print] تحويل كائن [$child1] إلى سلسلة. للقيام بذلك، تستخدم طريقة [__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: الكائن [$child1]؛
  • الأسطر 12-21: مصفوفة سمات الكائن [$child1]. لدينا كل هذه السمات؛
  • السطر 22: لدينا سلسلة JSON للكائن [$child1]؛
  • الأسطر 23-44: نفس الشيء بالنسبة للكائن [$child2]؛
  • الأسطر 45–112: بالنسبة للأب، الأمر مختلف قليلاً لأن سمة [children] الخاصة به ليست NULL، كما كان الحال بالنسبة للأبناء؛
  • السطر 112: نرى أن الأبناء مفقودون من سلسلة JSON للأب؛
  • الأسطر 79–111: نرى أنه في مصفوفة سمات الأب، يظل الطفل 1 (الأسطر 89–98) كائنًا، وكذلك الطفل 2 (الأسطر 99–110). باختصار، التعبير [\get_object_vars($this)]، حيث يمثل [$this] الوالد، ليس تكراريًا: إذا كانت سمة من فئة [Person] هي نفسها كائن، فإن التعبير [\get_object_vars($this)] لا يحاول استرداد مصفوفة سماتها؛

يمكننا تحسين هذا. نقوم بتعديل فئة [Person] إلى فئة [Person2] التالية:


<?php
 
class Personne2 {
  // attributes
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;
 
  // setter global
  public function setFromArray(array $arrayOfAttributes): Personne2 {

    // object is returned
    return $this;
  }
 
  // getters
  public function getNom() {
    return $this->nom;
  }
 

 
  // __toString
  public function __toString(): string {
    // retrieve the object's attributes
    $attributes = $this->getAttributes($this);
    $enfants = $attributes["enfants"];
    if ($enfants != NULL) {
      $attributes["enfants"] = [$enfants[0]->getAttributes(), $enfants[1]->getAttributes()];
    }
    // render the JSON attribute string
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }
 
  public function getAttributes(): array {
    return \get_object_vars($this);
  }
 
}

تعليقات

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

يستخدم البرنامج النصي [json-02.php] فئة [Person2] على النحو التالي:


<?php
 
// person2 class
require "Personne2.php";

// father instantiation
$père = new Personne2();
// initialization
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// instantiation and initialization child1
$enfant1 = (new Personne2())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 17
  ]);
// instantiation and initialization child2
$enfant2 = (new Personne2())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// father's children initialization
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);
 
// father display
print "------------------------père\n";
print "père=$père\n";

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

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

------------------------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}]}

هذه المرة، نجحنا في الحصول على الأبناء مع الأب.

الحل السابق غير مرضٍ لأن الأطفال قد يكون لديهم أطفالهم الخاصون. عندها نواجه المشكلة السابقة.

تحل فئة [Person3] هذه المشكلة على النحو التالي:


<?php
 
class Personne3 {
  // attributes
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;
 
  // setter global
  public function setFromArray(array $arrayOfAttributes): Personne3 {

  }
 
  // getters

 
  // __toString
  public function __toString(): string {
    // render the JSON attribute string
    $attributes = [];
    $this->getRecursiveAttributes($attributes, $this, []);
    // string JSON of attributes
    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 analysis [$value]
    // $keys is an array [key1, key2, .., keyn]
    // $value=$attributes[key1][key2]….[keyn]
    // if [$value] is an object, we use its method [getAttributes]
    if (\is_object($value)) {
      // attributs de l'objet [$value]
      $objectAttributes = $value->getAttributes();
      // what do we do with the result?
      if ($keys) {
        // in [$attributes], we replace $value with the array of its attributes
        // element $attributes[key1][key2]...[keyn] must be constructed
        // where $keys is the array [key1, key2, .., keyn]
        // we take the table reference [$attributes]
        $attribute = &$attributes;
        // scan the key table
        foreach ($keys as $key) {
          // we take the reference of the
          $attribute = &$attribute[$key];
        }
        // here $attribut and $attributes[key1][key2]...[key(n)] are identical
        // they share the same memory location
        // object [$value] is replaced by its array of attributes;
        // write $attributes[key1][key2]...[keyn]=$objectAttributes
        // which is equivalent to $attribute = $objectAttributes
        $attribute = $objectAttributes;
      } else {
        // no keys - we're just beginning to explore the object
        // $objectAttributes represents the 1st level attributes of the class
        $attributes += $objectAttributes;
      }
      // maybe in [$objectAttributes] there are still objects
      // explore [$objectAttributes] attributes
      $this->getRecursiveAttributes($attributes, $objectAttributes, $keys);
    } else {
      if (\is_array($value)) {
        // we have a table - we analyze each of its elements
        foreach ($value as $key => $élément) {
          // add the current key to the $keys array
          \array_push($keys, $key);
          // we analyze $élément
          $this->getRecursiveAttributes($attributes, $élément, $keys);
          // remove the key just analyzed from the $keys array
          \array_pop($keys);
        }
      }
    }
  }

تعليقات

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

يستخدم البرنامج النصي [json-03.php] فئة [Person3] على النحو التالي:


<?php
 
// person3 class
require "Personne3.php";
 
// father instantiation
$père = new Personne3();
// initialization
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// instantiation and initialization child1
$enfant1 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 27
  ]);
// instantiation and initialization child2
$enfant2 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// father's children initialization
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);
// instantiation and initialization child11
$enfant11 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Gaëtan",
  "âge" => 2
  ]);
// instantiation and initialization child12
$enfant12 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Mathilde",
  "âge" => 1
  ]);
// initialization children of child1
$enfant1->setFromArray([
  "enfants" => [$enfant11, $enfant12]
]);
// father display
print "------------------------père\n";
print "père=$père\n";
  • الأسطر 30-45: نخصص طفلين إلى [$child1]؛

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

------------------------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 لجميع كائنات [Person] التي تشكل الكائن الأصلي.