В 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, когда родитель полагается на то, что он ведёт себя ровно одним способом, например метод, который проверяет права перед любым действием.
Ловушка с private-методами
Закрытый метод не наследуется, поэтому метод дочернего класса с тем же именем его не переопределяет. Код родителя продолжает вызывать собственную закрытую версию, и это удивляет тех, кто ждёт, что выполнится метод дочернего класса:
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, когда он нужен дочерним классам.