في PHP يرث صنف من آخر بـ extends: تعطي class Dog extends Animal الصنف Dog كل خاصية وتابع عام ومحمي في Animal. يستطيع الابن إضافة توابع جديدة، وإعادة تعريف الموروثة بتعريف تابع بالاسم نفسه، واستدعاء الأصل بـ parent::method().
ليس لـ Dog باني، فيستخدم باني Animal، ولهذا تعمل new Dog('Rex'). لا يستطيع الصنف الابن أن يرث إلا أبًا واحدًا؛ فليس في PHP وراثة متعددة للأصناف.
استدعاء تابع الأب بـ parent::
إعادة التعريف تستبدل تابع الأب كاملًا. ولتوسيعه بدل استبداله، استدعِ parent::method() من الابن وابنِ على نتيجته:
يعمل الشيء نفسه مع البواني بـ parent::__construct(...)، التي تشرحها صفحة الباني، بما في ذلك ما يحدث عندما تنساها.
protected مقابل private في الأصناف الأبناء
العضو protected مخفي عن الشيفرة الخارجية لكنه مرئي لكل صنف في العائلة. أما العضو private فمرئي في الصنف الذي يعرّفه فقط، فلا يستطيع الصنف الابن رؤيته أصلًا:
تكون isset($this->pin) خاطئة في الابن لأنه لا يصل إلى $pin الخاصة بالأب، بينما ما زال تابع الأب نفسه hasPin() يقرؤها. استخدم protected لما يُراد أن تبني عليه الأصناف الفرعية، وprivate للتفاصيل التي تريد أن تبقى حرًا في تغييرها.
تعدد الأشكال وinstanceof
الشيفرة المكتوبة لنوع الأب تعمل مع كل ابن. ينفّذ كل كائن نسخته الخاصة من التابع المعاد تعريفه، فتتعامل حلقة واحدة معها كلها. وتتحقق instanceof من أن كائنًا من صنف ما أو من أي من أبنائه:
عندما لا يجب استخدام الأب وحده أبدًا (Shape عادي مساحته 0 لا معنى له تقريبًا)، اجعله صنفًا مجردًا وصرّح بأن area() مجرد.
قواعد إعادة التعريف: يجب أن تبقى التواقيع متوافقة
يجب أن يقبل التابع المعاد تعريفه على الأقل ما كان الأب يقبله، وأن يعيد شيئًا يستطيع مستدعو الأب استخدامه. يمكنك إضافة معامل اختياري، أو توسيع نوع معامل، أو تضييق نوع القيمة المعادة؛ ولا يمكنك إضافة معامل إلزامي أو جعل تابع عام خاصًا.
غيّر string $sep = ',' إلى string $sep إلزامية فترفض PHP ترجمة الصنف بخطأ فادح ("Declaration of CsvExporter::export(array sep): string must be compatible with Exporter::export(array $rows): string").
الأصناف والتوابع final
توقف final الوراثة عند تلك النقطة. لا يمكن إعادة تعريف التابع final، ولا يمكن وراثة final class. ويُفحص كلاهما عند ترجمة الملف، فلا يبدأ السكربت أصلًا:
<?php
class Payment
{
final public function charge(int $cents): string
{
return "charged $cents";
}
}
class FreePayment extends Payment
{
public function charge(int $cents): string
{
return 'free';
}
}
PHP Fatal error: Cannot override final method Payment::charge() in /home/index.php on line 12
للصنف final تكون الرسالة Class B cannot extend final class A. علّم تابعًا بـ final عندما يعتمد الأب على أن يتصرف بطريقة واحدة بالضبط، مثل تابع يتحقق من الصلاحيات قبل أي شيء آخر.
فخ التوابع الخاصة
التابع الخاص لا يُورث، لذا لا يعيد تابع ابن بالاسم نفسه تعريفه. وتظل الشيفرة في الأب تستدعي نسختها الخاصة، وهذا يفاجئ من يتوقعون أن يعمل تابع الابن:
ما زالت greet() تطبع Hello: تُحل $this->word() في الأب إلى تابع الأب الخاص. غيّر private إلى protected في Greeter وشغّله مرة أخرى؛ الآن صار تابع الابن إعادة تعريف حقيقية وتقول greet() كلمة Bonjour.
الأسئلة الشائعة
كيف تعمل extends في PHP؟
تجعل class Dog extends Animal الصنف Dog ابنًا لـ Animal. يملك كائن Dog كل خاصية وتابع عام ومحمي في Animal، ويستطيع إضافة ما يخصه، واستبدال (إعادة تعريف) التوابع الموروثة بتعريف تابع بالاسم نفسه.
كيف أستدعي تابع الأب في PHP؟
استخدم parent::methodName() داخل تابع الابن، مثل return parent::describe() . ' and more';. وللباني تكون parent::__construct(...).
هل تدعم PHP الوراثة المتعددة؟
لا. لا يستطيع الصنف أن يرث إلا صنفًا أبًا واحدًا. لمشاركة سلوك من عدة مصادر، نفّذ عدة واجهات (class A implements B, C) واستخدم السمات (use Loggable, Timestamps;).
ماذا تعني final في PHP؟
لا يمكن لصنف ابن إعادة تعريف التابع final، ولا يمكن وراثة final class أصلًا. وكسر أي من القاعدتين خطأ فادح عند ترجمة الصنف، مثل Cannot override final method A::f().
هل يستطيع الصنف الابن الوصول إلى خاصية خاصة في أبيه في PHP؟
لا. العضو private ملك للصنف الذي يعرّفه وحده، لذا تكون $this->pin في الابن خاصية غير معرّفة (تحذير، ثم null) ولا يعيد تابع ابن بالاسم نفسه تعريف التابع الخاص. صرّح بالعضو protected عندما تحتاج إليه الأصناف الأبناء.