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

Раздел: Объектно-ориентированное программирование в PHP -> ООП: наследование и интерфейсы

Наследование классов в PHP позволяет создавать иерархию, где дочерний класс перенимает свойства и методы родителя. Механизм extends упрощает повторное использование кода и строит полиморфные структуры.

Основы наследования с extends

Для создания класса-наследника используется ключевое слово extends. Пример:

class Animal {
    public $name;
    public function __construct($name) { $this->name = $name; }
    public function speak() { return $this->name . ' издаёт звук'; }
}
class Dog extends Animal {
    public function speak() { return $this->name . ' лает'; }
}
$dog = new Dog('Бобик');
echo $dog->speak(); // Бобик лает

Php class extends (наследование классов в php)

Класс Dog переопределил метод speak. Если нужно дополнить, а не заменить поведение родителя, используется parent:::

class Dog extends Animal {
    public function speak() {
        $parent = parent::speak();
        return $parent . ', но по-собачьи: гав';
    }
}

Результат: 'Бобик издаёт звук, но по-собачьи: гав'

Типичные ошибки:

  • Забыть вызвать parent::__construct() - свойства родителя не инициализируются.
  • Изменить сигнатуру метода (количество/типы параметров) - в PHP 8+ может вызвать фатальную ошибку при включённой строгой типизации.
  • Попытка обратиться к приватным свойствам родителя напрямую - только через protected или публичные методы.

Как запретить переопределение метода?

Используется модификатор final перед объявлением метода. Пример:

class ParentClass {
    final public function fixedMethod() { /* логика */ }
}
class ChildClass extends ParentClass {
    public function fixedMethod() { /* ошибка */ }
}

В результате - фатальная ошибка. Цель: гарантировать, что критическая логика не будет изменена.

Ошибка: Cannot override final method.

Как запретить наследование класса?

Класс объявляется с final:

final class FinalClass { /* ... */ }
class SubClass extends FinalClass {} // Fatal error

Применяется для классов, которые не должны расширяться (например, классы-синглтоны или конечные реализации).

Как заставить потомков реализовать определённый метод?

Создаётся абстрактный класс с абстрактным методом:

abstract class AbstractAnimal {
    abstract public function makeSound(): string;
}
class Cat extends AbstractAnimal {
    public function makeSound(): string { return 'Мяу'; }
}

Невозможно создать экземпляр абстрактного класса. Цель: определить общий контракт для группы классов.

Попытка new AbstractAnimal() вызывает фатальную ошибку. Также если наследник не реализует абстрактный метод - ошибка.

Как правильно наследовать конструктор?

Явный вызов parent::__construct() в конструкторе потомка:

class ParentClass {
    public function __construct($value) { $this->value = $value; }
}
class ChildClass extends ParentClass {
    public function __construct($value, $extra) {
        parent::__construct($value);
        $this->extra = $extra;
    }
}

Если не вызвать, значение $value не будет установлено. Цель: гарантировать инициализацию родительских свойств.

Ошибка: доступ к неопределённому свойству $this->value, если не вызван parent::__construct.

Как получить доступ к приватным данным родителя?

Приватные свойства видны только в классе, где определены. Решение: сделать свойство protected или предоставить публичный/защищённый метод доступа.

class ParentClass {
    private $secret = 'секрет';
    protected function getSecret() { return $this->secret; }
}
class ChildClass extends ParentClass {
    public function reveal() { return $this->getSecret(); } // работает
    public function fail() { return $this->secret; } // ошибка
}

Цель: инкапсуляция - родитель контролирует доступ к своим данным.

Расширенные примеры наследования

Пример 1. Цепочка наследования и parent:: с несколькими уровнями

Пример
class Grandparent {
    public function who() { return 'Дедушка'; }
}
class Parent extends Grandparent {
    public function who() { return 'Родитель (наследует: ' . parent::who() . ')'; }
}
class Child extends Parent {
    public function who() { return 'Ребёнок (наследует: ' . parent::who() . ')'; }
}
$obj = new Child();
echo $obj->who();
Ребёнок (наследует: Родитель (наследует: Дедушка))

Каждый уровень добавляет свой контекст, используя parent::who() из предыдущего.

Пример 2. Абстрактный класс с общей логикой и абстрактными методами

Пример
abstract class Shape {
    protected $color;
    public function __construct($color) { $this->color = $color; }
    abstract public function area(): float;
    public function info(): string { return 'Цвет: ' . $this->color . ', площадь: ' . $this->area(); }
}
class Circle extends Shape {
    private $radius;
    public function __construct($color, $radius) {
        parent::__construct($color);
        $this->radius = $radius;
    }
    public function area(): float { return M_PI * $this->radius ** 2; }
}
class Rectangle extends Shape {
    private $width, $height;
    public function __construct($color, $w, $h) {
        parent::__construct($color);
        $this->width = $w; $this->height = $h;
    }
    public function area(): float { return $this->width * $this->height; }
}
$circle = new Circle('красный', 3);
$rect = new Rectangle('синий', 4, 5);
echo $circle->info() . PHP_EOL;
echo $rect->info() . PHP_EOL;
Цвет: красный, площадь: 28.274333882308
Цвет: синий, площадь: 20

Абстрактный класс Shape содержит общее свойство color и метод info(), а area() оставляет наследникам.

Пример 3. Статические методы и позднее статическое связывание (static::)

Когда статический метод вызывается из наследника, self:: ссылается на класс, где метод определён, а static:: - на класс, из которого вызван.

Пример
class Ancestor {
    public static function who() { echo 'Ancestor'; }
    public static function testSelf() { self::who(); }
    public static function testStatic() { static::who(); }
}
class Descendant extends Ancestor {
    public static function who() { echo 'Descendant'; }
}
Descendant::testSelf();
Descendant::testStatic();
AncestorDescendant

Полезно для реализации паттерна Template Method в статическом контексте.

Пример 4. Комбинация extends и implements

Пример
interface LoggerInterface {
    public function log(string $msg): void;
}
class BaseLogger {
    protected $prefix = '[LOG]';
}
class FileLogger extends BaseLogger implements LoggerInterface {
    public function log(string $msg): void {
        echo $this->prefix . ' ' . $msg . PHP_EOL;
    }
}
$logger = new FileLogger();
$logger->log('Тестовое сообщение');
[LOG] Тестовое сообщение

Наследование даёт структуру данных, интерфейс гарантирует контракт.

Пример 5. Конструкторы с разным количеством аргументов

Пример
class A {
    public function __construct(...$args) {
        echo 'A создан с: ' . implode(', ', $args) . PHP_EOL;
    }
}
class B extends A {
    public function __construct($x, $y) {
        parent::__construct($x, $y);
        echo 'B создан с: ' . $x . ', ' . $y . PHP_EOL;
    }
}
$b = new B('first', 'second');
A создан с: first, second
B создан с: first, second

Наследование классов в PHP - comments

En
Php class extends (php)