Menu

Наследование в PHP: extends, parent:: и переопределение

В PHP класс наследует от другого через class Dog extends Animal: он получает публичные и защищённые свойства и методы родителя, может их переопределять и всё равно вызывать оригинал через parent::method(). Переопределение, protected, final, instanceof и ловушка с private-методами.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

В 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 rows,stringrows, string 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, когда он нужен дочерним классам.

Иллюстрация языков программирования Coddy

Учитесь программировать с Coddy

НАЧАТЬ