Skip to content

5. کلاس‌ها

واژگان: کلاس نوعی PHP است. متغیری از این نوع، شیء نامیده می‌شود. یک شیء نمونه‌ای (instance) از یک کلاس است.

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";
//مقدار object1 (آدرس شی مورد اشاره) را در object2 کپی می‌کند
// این دو متغیر سپس متفاوت هستند اما به یک شی اشاره می‌کنند
$obj2 = $obj1;
// obj2 را تغییر می‌دهد
$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

توضیحات

  • خط ۶: نشانه $obj->attr به ویژگی attr از متغیر $obj اشاره دارد. اگر این ویژگی وجود نداشته باشد، ایجاد می‌شود و بدین ترتیب متغیر $obj به یک شیء دارای ویژگی‌ها تبدیل می‌گردد. ما دیده‌ایم که سپس PHP به‌طور پیش‌فرض یک شیء از نوع stdClass ایجاد می‌کند؛
  • خط ۱۶: عبارت $obj2=$obj1، زمانی که $obj1 یک شیء باشد، یک کپی از اشیاء از طریق مرجع است: $obj2 و $obj1 ارجاع (آدرس) به یک شیء یکسان هستند. خود شیء را می‌توان از طریق هر یک از این ارجاعات تغییر داد؛
  • خطوط ۲۳–۲۷: هدف نشان دادن این است که $obj1 و $obj2 دو متغیر متفاوت هستند: آنها آدرس حافظه یکسانی ندارند:
    • $obj2=$obj1 مقدار $obj1 را در متغیر $obj2 کپی کرد (عملیات ۱ بالا). مقدار $obj1 نشانی یک شیء است. بنابراین، $obj1 و $obj2 به یک شیء اشاره می‌کنند. هنگامی که یک متغیر مانند $obj دستکاری می‌شود و به یک شی اشاره می‌کند، PHP شی مورد اشاره توسط متغیر $obj را دستکاری می‌کند. همان‌طور که در نمودار زیر نشان داده شده است، می‌توان دید که شیء مورد اشاره را می‌توان از طریق $obj1 یا $obj2 تغییر داد. این موضوع در خطوط ۴ و ۵ نتایج نشان داده شده است؛

Image

  • خط ۳۰: عبارت $obj3=&$obj2 به این معنی است که $obj2 و $obj3 در همان آدرس، یعنی [1 ci-dessousقرار دارند. می‌توان گفت که این دو متغیر نام‌های مستعار برای یک مکان حافظه یکسان هستند. هر دو به یک شیء، شیء A، اشاره می‌کنند که در زیر با نام [2] نشان داده شده است؛
    • عملیات $obj2 = new stdClass() باعث ایجاد یک شیء جدید، شیء B ([3 ci-dessous])، می‌شود و آدرس این شیء جدید به متغیر $obj2 تخصیص داده می‌شود. از آنجا که $obj2 و $obj3 دو نام‌نما برای یک مکان حافظه هستند، $obj3 نیز به شیء جدید، شیء B، اشاره می‌کند. این موضوع در خطوط 16–27 و 30–41 نتایج نشان داده شده است؛

Image

  • خطوط ۵۲–۵۴: نشان می‌دهند که یک شیء را می‌توان مانند یک فرهنگ لغت پیمایش کرد. کلیدهای فرهنگ لغت نام ویژگی‌ها هستند و مقادیر فرهنگ لغت، مقادیر همان ویژگی‌ها می‌باشند؛
  • خط ۵۱: تابع count را می‌توان روی یک شی اعمال کرد (با هشدار) اما برخلاف انتظار، تعداد ویژگی‌ها را بازنمی‌گرداند. بنابراین، یک شی شباهت‌هایی با یک دیکشنری دارد اما خود یک دیکشنری نیست؛

5.3. کلاس Person بدون هیچ ویژگی اعلام‌شده‌ای

اسکریپت [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]

توضیحات

  • خطوط ۳–۱۳: یک کلاس Personne تعریف کنید. کلاس قالبی است که اشیاء از آن ایجاد می‌شوند. این شامل ویژگی‌ها و توابعی است که به آن‌ها متد گفته می‌شود. ویژگی‌ها لازم نیست اعلام شوند؛
  • خطوط ۸–۱۱: متد identité مقادیر سه ویژگی را که در کلاس تعریف نشده‌اند نمایش می‌دهد. کلمه کلیدی $this به شیئی اشاره دارد که متد بر روی آن اعمال می‌شود؛
  • خط ۱۷: یک شیء از نوع Personne با نام $p ایجاد می‌شود. کلمهٔ کلیدی `new` برای ایجاد یک شیء جدید استفاده می‌شود. این عملیات یک مرجع به شیء ایجادشده (یعنی یک نشانی) بازمی‌گرداند. نحوهای مختلفی ممکن است: `new Personne()`, `new Personne`, `new personne`. نام کلاس نسبت به حروف بزرگ و کوچک حساس نیست؛
  • خطوط ۱۸–۲۰: سه ویژگی مورد نیاز متد `identité` در داخل شی `$p` ایجاد می‌شوند؛
  • خط ۲۲: متد identité از کلاس Personne بر روی شی $p اعمال می‌شود. در کد (خطوط ۸–۱۱) متد identité، $this به همان شیء‌ای اشاره می‌کند که $p به آن اشاره دارد؛

5.4. کلاس Person با ویژگی‌های اعلام‌شده

اسکریپت [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]

توضیحات

  • خطوط ۶–۸: ویژگی‌های کلاس به‌طور صریح با استفاده از کلمهٔ کلیدی var اعلام شده‌اند؛

5.5. کلاس Person با یک سازنده

مثال‌های قبلی کلاس‌های غیرمعمول 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]";
  }

}

// آزمایش
// ایجاد یک شیء Person
$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]

توضیحات

  • خطوط ۶–۵۰: کلاس Person؛
  • خطوط ۷–۹: ویژگی‌های خصوصی کلاس. این ویژگی‌ها تنها در داخل کلاس قابل مشاهده هستند. کلمات کلیدی دیگری که می‌توان استفاده کرد عبارتند از:
  • public: صفت را از خارج کلاس قابل مشاهده می‌کند،
  • protected: ویژگی را از داخل کلاس و کلاس‌های مشتق آن قابل مشاهده می‌کند؛
  • از آنجا که ویژگی‌ها خصوصی هستند، نمی‌توان از خارج کلاس به آن‌ها دسترسی داشت. بنابراین نوشتن کد زیر امکان‌پذیر نیست:
$p=new Personne() ;
$p->nom="Landru" ;

در اینجا، ما خارج از کلاس Personne هستیم. از آنجا که ویژگی nom خصوصی است، خط ۲ نادرست است. برای مقداردهی اولیه فیلدهای خصوصی شیء $p، دو راه وجود دارد:

  • از متدهای عمومی set و get (نام این متدها می‌تواند هر چیزی باشد) در خطوط ۱۲–۳۴ استفاده کنید. سپس می‌توانیم بنویسیم:
$p=new Personne() ;
$p->setNom("Landru") ;
  • از سازنده در خطوط ۳۷–۴۲ استفاده کنید. سپس به این صورت می‌نویسید:
$p=new Personne("Michel","Landru",44) ;

کد بالا به طور خودکار متد کلاس Personne با نام __construct را فراخوانی می‌کند؛

  • خط ۵۹: این خط، انتیت $p را به صورت یک رشته کاراکتری نمایش می‌دهد. برای این کار، از متد کلاس Personne به نام __toString (خطوط ۴۵–۴۷) استفاده می‌شود؛
  • تمام متدهای کلاس (فعالیت‌ها) با کلمه کلیدی `public` پیش‌وند شده‌اند که نشان می‌دهد این فعالیت در خارج از کلاس قابل مشاهده است. کلمات کلیدی دیگر که می‌توان استفاده کرد، همانند ویژگی‌ها و با همان معنا، `private` و `protected` هستند. بدون یک ویژگی دید صریح، فعالیت دارای دید ضمنی `public` است؛

5.6. کلاس Person با بررسی‌های اعتبار در سازنده

سازندهٔ یک کلاس مکان مناسبی برای بررسی صحت مقادیر اولیهٔ شیء است. با این حال، یک شیء می‌تواند از طریق متدهای set یا معادل آن نیز مقداردهی اولیه شود. برای جلوگیری از تکرار همان بررسی‌ها در دو مکان مختلف، می‌توان این بررسی‌ها را درون متدهای set قرار داد. اگر مقدار اولیه یک شی نادرست باشد، یک استثنا پرتاب خواهد شد. در اینجا یک مثال آمده است.

ابتدا، تعریف کلاس Personne را به فایل مستقل آن، [Personne.php]، منتقل می‌کنیم:


<?php

// پابندی سخت‌گیرانه به انواع اعلام‌شده‌ی پارامترهای تابع
declare (strict_types=1);

// فضای نام؛
namespace Exemples;

// کلاس Person
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]";
  }

}

توضیحات

  • خط ۴: مشخص می‌کند که هنگام اعلام، باید به نوع پارامترهای تابع پایبند بود؛
  • خط ۷: یک فضای نام تعریف می‌کند. بنابراین نام کاملاً واجد شرایط (که با نام qualifié شناخته می‌شود) برای کلاس Personne، \Exemples\Personne است. به کاراکتر \ در ابتدای نام واجد شرایط توجه کنید: این منجر به یک نام واجد شرایط absolu می‌شود. اگر این کاراکتر وجود نداشته باشد، نام واجد شرایط relatif (نسبت به فضای نام فعلی) خواهد بود. بنابراین، اگر دو کلاس A و B به یک فضای نام E تعلق داشته باشند، در کد کلاس A می‌توان با استفاده از نگاشت relative B به کلاس B دسترسی داشت. اگر کلاس A به فضای نام E1 و کلاس B به فضای نام E2 تعلق داشته باشد، آنگاه در کد کلاس A، می‌توان به کلاس B با استفاده از notation absolue \E2\ دسترسی داشت.B. تعریف یک کلاس درون یک فضای نام اجباری نیست، اما NetBeans اگر این کار را نکنید هشدار صادر می‌کند. بنابراین ما این کار را انجام می‌دهیم. علاوه بر این، فضاهای نام باید با ساختار فایل‌ها مطابقت داشته باشند. بنابراین، کلاس A در فضای نام E1 باید در فایلی با نام E1/A.php قرار گیرد. این امر اجباری نیست، اما باز هم، NetBeans در صورتی که این کار را انجام ندهید، هشدار صادر می‌کند. در مثال کلاس [\Exemples\PersonneNetBeans هشداری صادر می‌کند زیرا ساختار فایل [Personne.php]، [exemples/classes/Personne.php] است و بنابراین با فضای نام مطابقت ندارد. مسیر فایل نباید با فضای نام (namespace) اشتباه گرفته شود. نام کاملاً واجد شرایط یک کلاس از فضای نام استفاده می‌کند و هیچ ارتباطی با مسیر فایل کلاس (PHP) ندارد. ارتباط بین مسیر فایل و فضای نام اختیاری است و می‌توان از آن صرف‌نظر کرد، همان‌طور که ما در اینجا انجام داده‌ایم؛
  • خطوط ۱۲–۱۴: سه ویژگی خصوصی کلاس؛
  • خطوط ۲۹–۳۷: مقداردهی اولیه ویژگی prenom و بررسی مقدار اولیه؛
  • خط ۳۱: تابع trim($chaine) هرگونه فضای خالی را از ابتدای و انتهای $chaine حذف می‌کند. بنابراین، trim("abcd ") رشته «abcd» را برمی‌گرداند و trim" " رشته خالی را برمی‌گرداند؛
  • خط ۳۲: اگر نام اول خالی باشد، یک استثنا پرتاب می‌شود (خط ۳۳)؛ در غیر این صورت، نام اول ذخیره می‌شود (خط ۳۵). برای پرتاب یک استثنا، از کلاس از پیش تعریف‌شده [Exception] استفاده می‌شود. در اینجا مجبوریم از نام مطلق آن، [\Exceptionاستفاده کنیم. اگر از نام نسبی آن [Exception] استفاده کنیم، آنگاه این کلاس در فضای نام فعلی، یعنی فضای نام [Exemples] متعلق به کلاس Personne، جستجو خواهد شد. بنابراین، تفسیرگر PHP به دنبال کلاسی با نام مطلق [\Exemples\Exception] خواهد گشت که وجود ندارد؛

کلاس [Personne] توسط اسکریپت زیر [classes-05.php] استفاده می‌شود:


<?php

// پابندی سخت‌گیرانه به انواع پارامترهای تابع
declare(strict_types=1);

// شامل تعریف کلاس Person
require_once __DIR__."/Personne.php";

// نام واجد شرایط کلاس Person
use \Exemples\Personne;

// آزمایش
// ایجاد یک شیء Person
$p = new Personne("Paul", "Langevin", 48);
// هویت این شخص
print "Exemple1, personne=$p\n";
// ایجاد یک شیء Person نادرست
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";
}
// ایجاد یک شیء Person نادرست
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;

توضیحات

  • خط ۷: اسکریپت از کلاس [Personne] استفاده خواهد کرد. بنابراین باید به مفسر PHP بگوییم که تعریف این کلاس را کجا می‌تواند پیدا کند. این نقش دستورات [include fichier] و [require fichier] است. در اینجا از دستور [include] استفاده شده است. تفاوت بین این دو دستور به شرح زیر است: اگر دستور [include fichier] هنگام بارگذاری [fichier] با خطاهایی مواجه شود، خطایی در سطح [E_WARNING] صادر می‌شود، امااجرا ادامه می‌یابد، در حالی که [requireدر شرایط مشابه، یک خطای مرگبار ایجاد می‌کند و اجرای اسکریپت متوقف می‌شود. هر یک از این دو دستور، یک نوع دیگر نیز دارند: [include_once] و [require_once]. این دو متغیر امکان مدیریت چندین بار فراخوانی یک فایل را فراهم می‌کنند. می‌توان پروژه‌ای را تصور کرد که شامل چندین اسکریپت PHP باشد که تعدادی از آن‌ها به کلاس [Personne] ارجاع می‌دهند. اجرای این اسکریپت‌ها باعث می‌شود فایل [Personne.php] چندین بار وارد شود که این امر خطایی ایجاد می‌کند زیرا یک کلاس نمی‌تواند دو بار تعریف شود. راه‌حل استفاده از واریانت‌های [_once] است که تضمین می‌کنند فایل فقط یک بار در اسکریپت جهانی پروژه وارد شود؛
  • خط ۷: مقدار ثابت [__DIR__] یک مقدار ثابت PHP است که به نام کامل پوشه‌ای که حاوی اسکریپت با مقدار ثابت [__DIR__] است، اشاره می‌کند. بنابراین، عبارت در خط ۱۷:

require_once __DIR__."/Personne.php";

معادل چیزی شبیه به این خواهد بود:


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

در مسیر فایل، کاراکترهای / و \ را می‌توان به‌طور متناوب استفاده کرد؛

  • خط ۱۴: ما از کلاسی به نام [Personne] که همین حالا تعریف کرده‌ایم، استفاده می‌کنیم. اسکریپت [classes-05.php] فضای نام ندارد. خط ۱۴ از نام نسبی کلاس [Personne] بدون فضای نام استفاده می‌کند. از آنجا که کلاس [Personne] فضای نام ندارد، در داخل خود اسکریپت [classes-05.php] جستجو می‌شود و بنابراین پیدا نخواهد شد. دو راه‌حل برای این مشکل وجود دارد:
    • از نام کامل کلاس [\Exemples\Personne] استفاده کنید؛
    • از دستور `use` در خط ۱۰ استفاده کنید. این نشان می‌دهد که کد بعدی از کلاس `[\Exemples\Personne]` استفاده می‌کند؛
  • خط ۱۰: دستور `use` به تفسیرگر می‌گوید که کلاس `[Personne]` که در خط ۱۴ به آن ارجاع شده است، در واقع کلاس `[\Exemples\Personne]` است. با این حال، تفسیرگر کد این کلاس را از کجا پیدا خواهد کرد؟ خط ۷ به آن می‌گوید. این خط مشخص می‌کند که برای اجرای اسکریپت جاری، اسکریپت [Personne.php] نیز باید بارگذاری شود. در اینجا از نام فایل نسبی استفاده شده است. بنابراین، در پوشه‌ای که حاوی اسکریپت [classes-05.php] است، به دنبال آن گشته‌ خواهد شد. بنابراین اسکریپت‌های [Personne.php] و [classes-05.php] باید در همان پوشه باشند. در اینجا این وضعیت برقرار است، زیرا هر دو در پوشه [exemples/classes] قرار دارند. دستور در خط ۱۰ معادل است با:

use \Exemples\Personne as Personne;

دستور [use] در بالا بیان می‌کند که نام مستعار [Personne] به کلاس [\Exemples\Personne] اشاره دارد؛

  • خط ۱۴: یک شیء [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 در خطوط ۱۸ تا ۲۴ شامل دو بلاک catch است تا دو نوع استثنا را به‌طور جداگانه مدیریت کند؛

  • خط ۲۰: می‌توان به صورت [Exception] یا [\Exception] نوشت:
    • نسخه اول از نام کلاس نسبی، نسبت به فضای نام اسکریپت استفاده می‌کند. این اسکریپت فضای نام مخصوص به خود ندارد. بنابراین فضای نام آن ریشه همه فضاهای نام است: \. بنابراین، نوشتن [Exception] در اینجا معادل نوشتن [\Exception] است. با این حال، کلاس [Exception] در واقع در فضای نام [\] قرار دارد؛

ترجیح است از نام مطلق استثناهای از پیش تعریف‌شده، PHP، در اسکریپتی که خود هیچ فضای نامی ندارد، استفاده شود. بنابراین، اگر تصمیم بگیرید به این اسکریپت یک فضای نام اختصاص دهید، نوشتن نام‌های کلاس مطلق همچنان معتبر باقی می‌ماند، در حالی که در حالت دیگر، تغییر فضای نام باعث خطا در نام‌های کلاس نسبی خواهد شد؛

  • خط ۲۱: هنگامی که یک استثنا رخ می‌دهد، متد [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;
    }
}

در خط ۵، یک استثنا با پیام خطای [Le prénom doit être non vide] پرتاب می‌شود. این همان چیزی است که متد [Exception→getMessage] در خط ۲۹ از اسکریپت [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) {
    // شیء فعلی را با یک Person مقداردهی اولیه می‌کند $p
    $this->__construct($p->prenom, $p->nom, $p->age);
}

توضیحات

  • خطوط ۲–۵: متد initWithPersonne اجازه می‌دهد مقادیر ویژگی‌های یک شیء دیگر، Personne، به شیء فعلی اختصاص داده شود. در اینجا، متد سازنده __construct را فراخوانی می‌کند، اما این امر اجباری نیست. می‌توانست ویژگی‌های کلاس [Personne] را خودِ کلاس مقداردهی اولیه کند؛

کلاس جدید [Personne] توسط اسکریپت زیر [classes-06.php] استفاده می‌شود:


<?php

// شامل تعریف کلاس Person
require_once __DIR__."/Personne.php";
// اعلام نام واجد شرایط کلاس Person
use \Exemples\Personne;

// آزمایش
// ایجاد یک شیء Person
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;
  • خطوط ۱۴، ۲۳، ۳۰: معمول است که پس از یک استثنا، اجرای یک اسکریپت کنسول باید در صورتی که خطای پیش‌آمده غیرقابل بازیابی باشد، متوقف شود. این امر در یک اسکریپت وب صادق نیست: اجرای اسکریپت متوقف نمی‌شود، بلکه یک صفحه خطا نمایش داده می‌شود. اگر در داخل یک تابع باشد، به جای `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;

// آزمایش
// ایجاد آرایه‌ای از اشیاء Person
// برای ساده‌تر شدن درک کد، هیچ استثنای احتمالی را مدیریت نمی‌کنیم
$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]

نظرات

  • خط ۹: ایجاد آرایه‌ای از ۲ نفر؛
  • خط ۱۲: تکرار در آرایه؛
  • خط ۱۳: $groupe[$i] یک شی از نوع Personne است. متد [Personne.__toString] برای نمایش آن استفاده می‌شود؛

5.9. ایجاد کلاسی مشتق‌شده از کلاس Person

در فایلی به نام [Enseignant.php کلاس زیر را با نام [Enseignant] ایجاد کنید:


<?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]";
  }

}

توضیحات

  • خط ۷: کلاس [Enseignant] همچنین بخشی از فضای نام [Exemples] است؛
  • خط ۱۰: کلاس Enseignant کلاس Personne. را گسترش می‌دهد. کلاس مشتق Enseignant ویژگی‌ها و متدهای کلاس والد خود را به ارث می‌برد؛
  • خط ۱۲: کلاس Enseignant یک ویژگی جدید به نام discipline را اضافه می‌کند که مختص خود آن است؛
  • خط ۲۵: سازنده کلاس Enseignant چهار پارامتر می‌گیرد:
    • ۳ پارامتر برای مقداردهی اولیه کلاس والدین آن (نام، نام خانوادگی، سن)، خط ۲۷؛
    • ۱ برای inizialization خود (موضوع)، خط ۲۹؛
  • خط ۲۷: کلاس مشتق شده از طریق کلمه کلیدی `parent::` به متدها و سازنده‌های کلاس والد خود دسترسی دارد. در اینجا، پارامترها (نام، نام خانوادگی، سن) به سازنده کلاس والد ارسال می‌شوند؛
  • خطوط ۳۳–۳۵: متد `__toString` از کلاس مشتق از متد `__toString` در کلاس والد استفاده می‌کند؛

کلاس [Enseignant] توسط اسکریپت زیر، [classes-08.php استفاده می‌شود:


<?php

// شامل تعاریف هر دو کلاس
require_once __DIR__."/Personne.php";
require_once __DIR__."/Enseignant.php";
// اعلام دو کلاس مورد استفاده
use \Exemples\Personne;
use \Exemples\Enseignant;

// آزمایش
// ایجاد آرایه‌ای از اشیاء Person و کلاس‌های مشتق
//به منظور سادگی در این مثال، استثناها مدیریت نمی‌شوند
$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;

توضیحات

  • خطوط ۴–۵: ما باید به تفسیرگر PHP بگوییم که دو کلاس [Enseignant, Personne] در کجا قرار دارند؛
  • خطوط ۷–۸: اعلام نام کامل دو کلاس. این کار به ما اجازه می‌دهد تا در کد، صرفاً با نام آن‌ها و بدون پسوندهای فضای نام، به آن‌ها ارجاع دهیم؛
  • خط ۱۳: ما یک آرایه ایجاد می‌کنیم که شامل یک نوع [Personne] و یک نوع [Enseignant] است؛
  • خطوط ۱۶–۱۸: نمایش عناصر آرایه؛
  • خط ۱۷: متد __toString برای هر عنصر $groupe[$i] فراخوانی می‌شود. کلاس Personne دارای متدی به نام __toString است. کلاس Enseignant دو متد دارد: متد کلاس والد و متد خودِ کلاس. ممکن است این سؤال پیش بیاید که کدام یک فراخوانی خواهد شد. اجرا نشان می‌دهد که متد کلاس Enseignant فراخوانی شده است. این امر همیشه صادق است: هنگامی که یک متد روی یک شی فراخوانی می‌شود، به ترتیب زیر در آن جستجو می‌شود: در خود شی، در کلاس والد آن در صورت وجود، سپس در کلاس والدِ والد، و غیره... جستجو به محض یافتن متد متوقف می‌شود.

نتایج:

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

5.10. ایجاد یک کلاس دوم مشتق شده از کلاس Person

مثال زیر کلاسی به نام Etudiant را که از کلاس Personne مشتق شده است، در فایل [Etudiant.php] ایجاد می‌کند:


<?php

// پابندی سخت‌گیرانه به انواع اعلام‌شده‌ی پارامترهای تابع
declare (strict_types=1);

// فضای نام
namespace Exemples;

class Etudiant extends Personne {
  // ویژگی‌ها
  private $formation;   // آموزش انجام‌شده

  // گیرنده و تنظیم‌کننده
  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();
    // کد مخصوص کلاس3
    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";

نظرات

  • خطوط ۷–۱۷: کلاس [Classe1] دو متد f و g را تعریف می‌کند؛
  • خطوط ۲۰–۲۷: کلاس [Classe2] کلاس [Classe1] را گسترش می‌دهد و متد f آن را مجدداً تعریف می‌کند؛

نتایج

1
2
3
11
2
1

توضیحات

  • خط ۳۰ کد یک شیء $c2 از نوع [Classe2] ایجاد می‌کند؛
  • خط ۳۱ کد متد f شیء $c2 را فراخوانی می‌کند. از آنجایی که این متد وجود دارد، اجرا می‌شود؛
  • خط ۳۲ کد متد g را در شی $c2 فراخوانی می‌کند. از آنجایی که این متد وجود ندارد، در کلاس والد جستجو می‌شود، که در آنجا پیدا شده و اجرا می‌گردد؛
  • خط ۳۳ کد یک شیء $c1 از نوع [Classe1] ایجاد می‌کند؛
  • خط ۳۴ کد، متد 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;
  }

}

// پارامتر تابع از نوع Class1 یا یک کلاس مشتق است
function doSomething(Classe1 $c1): void {
  print $c1->f() + $c1->g() . "\n";
}

// کد
//یک شیء از نوع Class2 که از Class1 مشتق شده است، ایجاد می‌شود
$c2 = new Classe2();
// doSomething با
doSomething($c2);

توضیحات

  • خطوط ۷–۱۷: کلاس [Classe1];
  • خطوط ۲۰–۲۷: کلاسی به نام [Classe2] که از [Classe1] ارث‌بری شده است؛
  • خط ۳۰: تابعی که انتظار یک پارامتر از نوع [Classe1] را دارد. هنگامی که نوع مورد انتظار یک کلاس است، پارامتر واقعی می‌تواند یک شیء از نوع مورد انتظار یا یک نوع مشتق باشد؛
  • خطوط ۳۵–۳۸: تابع [doSomething] با پارامتری از نوع [Classe2] فراخوانی شده است، در حالی که نوع مورد انتظار [Classe1] است؛

نتایج

13

5.14. کلاس‌های انتزاعی

یک کلاس انتزاعی یک کلاس ناقص است که نمی‌توان آن را نمونه‌سازی کرد. برای قابل استفاده بودن، باید از آن ارث بری شود.

هدف از یک کلاس انتزاعی چیست؟ گاهی اوقات کلاس‌هایی داریم که یک یا چند متد را مشترکاً دارند اما از نظر متدها یا ویژگی‌های دیگر با هم متفاوت هستند. در چنین مواردی، توصیه می‌شود که همه چیزهای مشترکی را که دارند در یک کلاس والد گروه بندی کنیم. در حال حاضر، به یک کلاس انتزاعی نیاز نداریم. اما فرض کنید که کلاس‌های فرزند فقط در یک متد، M، با هم تفاوت دارند: امضای متد در تمام کلاس‌های فرزند یکسان خواهد بود، اما پیاده‌سازی آن متفاوت خواهد بود. برای اینکه از کلاس‌های فرزند بخواهیم متد M را پیاده‌سازی کنند:

  • ما امضای متد M را در کلاس والد اعلام می‌کنیم. از آنجا که کلاس والد نمی‌داند چگونه آن را پیاده‌سازی کند، متد را با کلیدواژه `abstract` پیش‌وند می‌دهیم: این بدان معناست که پیاده‌سازی متد M به کلاس‌های فرزند واگذار می‌شود؛
  • از آنجایی که کلاس والد به طور کامل پیاده‌سازی نشده است، آن نیز با استفاده از همان کلمه کلیدی `abstract` به صورت انتزاعی (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";

توضیحات

  • خطوط ۷–۱۶: کلاس [Classe1] انتزاعی است (خط ۷) زیرا متد g را در خط ۱۵ پیاده‌سازی نمی‌کند. بنابراین برای قابل استفاده شدن باید از کلاسی دیگر ارث ببرد؛
  • خطوط ۱۹–۲۶: کلاس [Classe2] کلاس [Classe1] را گسترش می‌دهد و متد g را از کلاس والد خود (خطوط ۲۲–۲۴) مجدداً تعریف می‌کند؛
  • خطوط ۲۹–۴۱: کلاس [Classe3] کلاس [Classe1] را گسترش می‌دهد و متد g کلاس والد خود را بازتعریف می‌کند (خطوط ۳۲–۳۴);
  • خطوط ۳۷–۳۹: کلاس [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();

توضیحات

  • خطوط ۷–۹: کلمه کلیدی `final` کلاس `[Classe1]` را به یک کلاس نهایی تبدیل می‌کند که نمی‌توان از آن ارث بری کرد؛
  • خطوط ۱۲–۱۴: کلاس [Classe2] از کلاس نهایی [Classe1] ارث می‌برد، که این یک خطا است؛
  • خط ۱۷: خطا تنها زمانی گزارش می‌شود که اسکریپت اجرا شود، در هنگام تلاش برای دستکاری یک شی از نوع [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();

توضیحات

  • خط ۱۳: متد f از کلاس [Classe1] با استفاده از کلمه کلیدی final به‌عنوان نهایی اعلام شده است؛
  • خط ۲۰: کلاس [Classe2] از کلاس [Classe1] ارث می‌برد؛
  • خطوط ۲۲–۲۳: تابع f از کلاس والد [Classe1] بازتعریف شده است. این باید باعث ایجاد خطا شود؛
  • خط ۲۹: یک شیء از نوع [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";
  }

}

// test -------------------
Classe1::say("hello");

توضیحات

  • خط ۶: متد [say] با استفاده از کلمه کلیدی static به‌عنوان ایستا (static) اعلام شده است؛
  • خط ۱۳: فراخوانی متد استاتیک [say] با استفاده از سینتکس: Class1::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";
  }

}

// test -------------------
new Classe1();
new Classe1();
Classe1::say();

توضیحات

  • خط ۵: یک ویژگی ایستا برای شمارش تعداد نمونه‌های کلاس [Classe1] که ایجاد شده‌اند، اعلام شده است. این یک ویژگی نیست که بتواند به یک نمونه از کلاس تعلق داشته باشد. در واقع، اگر دو شیء O1 و O2 ایجاد شوند، هیچ‌کدام از دیگری مطلع نخواهد بود. داشتن یک شمارنده در داخل نمونه بی‌معنی است: وقتی یک شیء جدید ایجاد می‌شود، در کدام نمونه شمارنده افزایش می‌یابد؟ ما مجبور خواهیم شد شمارنده یک شیء خاص را افزایش دهیم و شمارنده‌های نمونه‌های دیگر را نادیده بگیریم. یک ویژگی ایستا یک ویژگی کلاس است، نه یک ویژگی نمونه از کلاس؛
  • خطوط ۷–۱۰: در سازنده است که اشیاء ایجاد شده را می‌شماریم، زیرا ایجاد هر شیء جدید، اجرای سازنده را تحریک می‌کند؛
  • خط ۱۴: به نگارش `self::$nbObjects` توجه کنید، که نشان می‌دهد ما به یک ویژگی ایستا از کلاسی اشاره داریم که کد اجرا شده در آن قرار دارد؛
  • خطوط ۱۳–۱۵: متد استاتیک [say] برای نمایش تعداد اشیاء ایجاد شده استفاده می‌شود؛
  • خطوط ۲۰–۲۲: دو شیء ایجاد می‌شوند و شمارشگر اشیاء نمایش داده می‌شود؛

نتایج


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);
    // view والد
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }

}

class SomeChild extends SomeParent {
  // ویژگی
  private $attributeOfChild = 14;

  // روش
  public function doTest(): void {
    // view فرزند
    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();

توضیحات

  • خطوط ۳–۱۷: کلاس [SomeParent];
  • خطوط ۱۹–۳۲: کلاس فرزند [SomeChild]. می‌بینیم که این کلاس، کلاس [SomeParent] را گسترش می‌دهد (خط ۱۹);
  • خط ۵: کلاس [SomeParent] تنها یک ویژگی دارد؛
  • خطوط ۸–۱۷: متد [SomeParent::doTest] برای نمایش دو ویژگی طراحی شده است:
    • [$attributeOfParent]، که به کلاس [SomeParent] تعلق دارد؛
    • [$attributeOfChild]، که به کلاس [SomeChild] (خط 21) تعلق دارد؛
  • خطوط ۱۰–۱۱: هویت فراخواننده نمایش داده می‌شود؛ متد در واقع به دو روش متفاوت فراخوانی خواهد شد:
    • از کلاس والد [SomeParent];
    • از کلاس فرزند [SomeChild];
  • خطوط ۱۳–۱۴: نمایش دو ویژگی؛
  • خطوط ۱۹–۳۲: کلاس فرزند [SomeChild] که کلاس [SomeParent] (خط ۱۹) را گسترش می‌دهد؛
  • خط ۲۱: کلاس [SomeChild] تنها یک ویژگی دارد؛
  • خط 24: متد [SomeChild::doTest] برای نمایش دو ویژگی طراحی شده است:
    • [$attributeOfParent]، که متعلق به کلاس [SomeParent] است؛
    • [$attributeOfChild]، که متعلق به کلاس [SomeChild] است؛
  • خطوط ۲۶–۲۷: نمایش دو ویژگی؛
  • خط ۳۰: متد [doTest] از کلاس والد فراخوانی می‌شود که به نوبه خود هر دو ویژگی را نمایش می‌دهد؛
  • خط ۳۶: متد [SomeParent::doTest] فراخوانی می‌شود؛
  • خط ۳۸: متد [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

نظرات

  • خطوط ۱–۱۰: نتایج اولین تست که در آن متد [SomeParent::doTest] فراخوانی می‌شود؛
  • خطوط ۳–۶: می‌بینیم که شیئی که متد را فراخوانی می‌کند از نوع [SomeParent] است؛
  • خط ۷: ویژگی [$attributeOfParent] نمایش داده می‌شود؛
  • خطوط ۹–۱۰: می‌بینیم که ویژگی [SomeParent::$attributeOfChild] وجود ندارد. بنابراین نمایش داده نمی‌شود؛
  • خطوط ۱۱–۳۰: نتایج تست دوم، که در آن متد [SomeChild::doTest] فراخوانی می‌شود؛
  • خطوط ۱۳–۱۴: می‌بینیم که ویژگی [SomeChild::$attributeOfParent] وجود ندارد. بنابراین نمایش داده نمی‌شود. این طبیعی است: ویژگی [SomeParent::$attributeOfParent] همان [private] است و بنابراین در کلاس فرزند شناسایی نمی‌شود؛
  • خط ۱۵: ویژگی [$attributeOfChild] نمایش داده می‌شود؛
  • خطوط ۱۶–۳۰: ما در متد [SomeParent::doTest] هستیم که توسط کلاس فرزند فراخوانی شده است؛
  • خطوط 17–22: می‌بینیم که [$this] از نوع [SomeChild] با دو ویژگی خصوصی است؛
  • خط ۲۳: به طور شگفت‌آور، [$this] که از نوع [SomeChild] است، می‌تواند ویژگی کلاس والد خود، [$attributeOfParent] را ببیند؛
  • خطوط ۲۵–۳۰: به همان اندازه شگفت‌آور، [$thisاز نوع [SomeChildویژگی [$attributeOfChild] خود را نمایش نمی‌دهد؛

این نتیجه بسیار شگفت‌آور است: اگرچه خطوط 17–21 نشان می‌دهند که [$this] از نوع [SomeChild] است، این [$this] در روش [SomeParent::doTest] طوری رفتار می‌کند که گویی نمونه‌ای از کلاس [SomeParent] است نه کلاس [SomeChild].

بیایید یک تست جدید را با اسکریپت [classes-20.php] اجرا کنیم. ویژگی [$attributeOfParent] اکنون دارای دید [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 {
  // ویژگی
  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

نظرات

  • خط ۱۲: کلاس [SomeChild] اکنون می‌تواند ویژگیِ عنصر والد خود [$attributeOfParent] را ببیند. این امر طبیعی است، زیرا عنصر والد اکنون دارای دامنهٔ [protected] است؛
  • در متد [someParent::doTest]، شیء [$this] از نوع [SomeChild] است (خطوط ۱۵–۲۰). این شیء می‌تواند ویژگی والد خود، [$attributeOfparent] (خط ۲۱)، را ببیند، اما همچنان نمی‌تواند ویژگی خود، [$attributeOfChild] (خطوط ۲۳–۲۸)، را ببیند؛

در سومین تلاش، ویژگی [$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 {
    // view فرزند
    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
  • خط ۲۲: این بار، در داخل parent، [$this] که از نوع [SomeChild] (خطوط ۱۵–۲۰) است، می‌تواند به ویژگی محافظت‌شده [$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);
  }

}

نظرات

  • خطوط ۵–۸: چهار ویژگی کلاس؛
  • خطوط ۲۰–۳۵: گترهایی که به ما امکان بازیابی مقادیر این ویژگی‌ها را می‌دهند؛
  • خطوط ۱۱–۱۸: یک متد تنظیم‌کنندهٔ سراسری که امکان مقداردهی ویژگی‌ها از یک آرایهٔ انجمنی [$arrayOfAttributes] را فراهم می‌کند که کلیدهای آن با ویژگی‌های کلاس مطابقت دارند؛
  • خطوط ۳۸–۴۶: متد [__toString] کلاس؛
  • خط ۴۲: تابع PHP [get_object_vars] مقادیر ویژگی‌های کلاس را به صورت یک آرایهٔ انجمنی ['نام خانوادگی'=>'surname1', 'نام'=>'first name1', 'سن'=>'age1', 'فرزندان'=>[]]] بازیابی می‌کند؛
  • خط ۴۵: رشته jSON از این آرایهٔ ویژگی‌ها بازگردانده می‌شود؛

بیایید اسکریپت [json-01.php] را بررسی کنیم که از کلاس [Personne] استفاده می‌کند:


<?php

//کلاس Person
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";

توضیحات

  • خطوط ۶–۱۳: یک شیء از نوع [Personne] [$père] با استفاده از متد [Personne::setFromArray] مقداردهی اولیه می‌شود، که اجازه می‌دهدیک شیء [Personne] را با آرایه‌ای که کلیدهای آن با ویژگی‌های کلاس [Personne] مطابقت دارد، مقداردهی اولیه می‌کند؛
  • خطوط 14–19: یک شیء از نوع [Personne] [$enfant1] به همان روش مقداردهی اولیه می‌شود؛
  • خطوط 21–25: یک شیء از نوع [Personne] [$enfant2] مقداردهی اولیه می‌شود؛
  • خطوط 27–29: ویژگی [$père→enfants] با آرایه‌ای حاوی دو فرزند مقداردهی اولیه می‌شود؛
  • خطوط ۳۲–۳۳: دو فرزند به والد اختصاص داده می‌شوند؛
  • خط ۳۵: عملیات [print] تلاش می‌کند شیء [$enfant1] را به یک رشته کاراکتری تبدیل کند. برای این کار، از متد شیء [__toString] استفاده می‌کند. بنابراین انتظار داریم رشتهٔ شیء jSON را مشاهده کنیم؛
  • خطوط ۳۸–۳۹: ما همین کار را با والد انجام می‌دهیم؛

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


------------------------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":[{},{}]}

توضیحات

  • خطوط ۲–۱۱: شیء [$enfant1];
  • خطوط ۱۲–۲۱: آرایهٔ ویژگی‌ها برای شیء [$enfant1]. ما همهٔ آن‌ها را داریم؛
  • خط ۲۲: ما در واقع رشته jSON را برای شیء [$enfant1] داریم؛
  • خطوط ۲۳–۴۴: همین موضوع در مورد شیء [$enfant2] نیز صدق می‌کند؛
  • خطوط ۴۵–۱۱۲: برای والد، این کمی متفاوت است، زیرا ویژگی [enfants] آن، NULL نیست، همانطور که برای فرزندان بود؛
  • خط ۱۱۲: می‌بینیم که در رشته پدر jSON، فرزندان وجود ندارند؛
  • خطوط ۷۹–۱۱۱: می‌توانیم ببینیم که در جدول ویژگی‌های پدر، فرزند ۱ (خطوط ۸۹–۹۸) همچنان یک شیء باقی مانده است، و همین‌طور فرزند ۲ (خطوط ۹۹–۱۱۰). به طور خلاصه، عبارت [\get_object_vars($this)]، که در آن [$this] نماینده پدر است، بازگشتی (recursive) نیست: اگر یکی از ویژگی‌های کلاس [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);
  }

}

توضیحات

  • خطوط ۳۶–۳۸: تابع [getAttributes] آرایهٔ ویژگی‌های شیئی را که آن را فراخوانی می‌کند، بازمی‌گرداند؛
  • خطوط ۲۵–۳۴: تابع [__toString];
  • خط ۲۷: ویژگی‌های کلاس [Personne] در آرایه [$attributes] بازیابی می‌شوند؛
  • خط ۲۸: از مثال قبلی می‌دانیم که [$attributes["enfants"]] آرایه‌ای از دو شیء از نوع [Personne] است؛
  • خطوط ۲۹–۳۱: دو شیء با آرایه ویژگی‌هایشان جایگزین می‌شوند؛
  • خط ۳۳: تنها کاری که باقی مانده این است که آرایهٔ ویژگی‌های ساخته‌شده را به صورت jSON رمزگذاری کنیم؛

اسکریپت [json-02.php] از کلاس [Personne2] به شرح زیر استفاده می‌کند:


<?php

//کلاس Person2
require "Personne2.php";

// مثال‌سازی والد
$père = new Personne2();
// ابتدایی‌سازی
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// نمونه سازی و مقداردهی اولیه child1
$enfant1 = (new Personne2())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 17
  ]);
// مصداق‌سازی و инициализация فرزند دوم
$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]=$objectttributes
        //که معادل $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);
        }
      }
    }
  }

نظرات

  • خطوط ۲۱–۲۲: این بار، متد [__toString] ویژگی‌های کلاس خود را بازیابی می‌کند و نیاز دارد که این کار به‌صورت بازگشتی انجام شود: اگر یک ویژگی یک شیء یا آرایه‌ای از اشیاء باشد، آنگاه هر شیء باید در آرایه نهایی ویژگی‌های کلاس با آرایه ویژگی‌های خود جایگزین شود؛
  • خطوط ۳۱–۷۸: تابع [getRecursiveAttributes] این کار را انجام می‌دهد. این کد کامنت شده است. نوشتن یک تابع بازگشتی اغلب کار پیچیده‌ای است. در اینجا نیز همین‌طور است. اگر خواننده آن را درک نکند، چیزی از دست نخواهد داد. کتابخانه‌های موجودی وجود دارند که این کار را انجام می‌دهند. فراخوانی‌های بازگشتی در خطوط 64 و 72 رخ می‌دهند؛
  • مزیت این کد این است که صرفاً برای کلاس [Personne3] نوشته نشده است. این کد برای هر کلاسی با ویژگی‌هایی که مقادیرشان از انواع اشیاء مختلف هستند، معتبر است، به شرطی که کلاس‌های مورد استفاده توسط کلاس اصلی نیز متد [getAttributes] را که در خطوط 27–29 یافت می‌شود، داشته باشند.

اسکریپت [json-03.php] از کلاس [Personne3] به شرح زیر استفاده می‌کند:


<?php

//کلاس Person3
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]
]);
// مصداق‌سازی و آغازسازی child11
$enfant11 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Gaëtan",
  "âge" => 2
  ]);
// مصداق‌سازی و инициализация فرزند ۱۲
$enfant12 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Mathilde",
  "âge" => 1
  ]);
// ابتدایی‌سازی فرزندان child1
$enfant1->setFromArray([
  "enfants" => [$enfant11, $enfant12]
]);
//نمایش والد
print "------------------------père\n";
print "père=$père\n";
  • خطوط ۳۰–۴۵: دو فرزند به [$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] که والد را تشکیل می‌دهند، بازیابی کرده‌ایم.