Наследование и переопределение методов
Что такое наследование
Наследование — класс получает свойства и методы другого класса.
class Animal
{
public function eat(): void
{
echo 'Ест';
}
}
class Dog extends Animal
{
public function bark(): void
{
echo 'Гав';
}
}
$dog = new Dog();
$dog->eat(); // Ест (унаследовано)
$dog->bark(); // Гав
extends — наследование.
Родитель, потомок
Другие названия:
- Родитель = base, super, parent.
- Потомок = child, derived, subclass.
Что наследуется
Всё, кроме:
private— видны только в родителе.- Конструктор — если у родителя нет аргументов, вызывается автоматически.
Наследуется:
publicиprotectedметоды.publicиprotectedсвойства.- Константы.
Переопределение методов
Метод с тем же именем:
class Animal
{
public function speak(): string
{
return 'Звук';
}
}
class Dog extends Animal
{
public function speak(): string
{
return 'Гав';
}
}
$dog = new Dog();
echo $dog->speak(); // Гав
Переопределение — замена поведения.
parent::
Вызвать метод родителя:
class Animal
{
public function speak(): string
{
return 'Звук';
}
}
class Dog extends Animal
{
public function speak(): string
{
return parent::speak() . ' (но гав)';
}
}
$dog = new Dog();
echo $dog->speak(); // Звук (но гав)
parent::method() — расширение, не замена.
Конструктор родителя
Вызывать явно:
class BaseEntity
{
public function __construct(
public readonly int $id,
) {}
}
class User extends BaseEntity
{
public function __construct(
int $id,
public readonly string $email,
) {
parent::__construct($id);
}
}
Если родитель имеет обязательные параметры — обязательно.
Сигнатуры методов
Правила (LSP):
- Параметры можно расширить (контравариантность).
- Возврат можно сузить (ковариантность).
Пример возврата:
class Animal
{
public function clone(): Animal { /* ... */ }
}
class Dog extends Animal
{
public function clone(): Dog { /* ... */ } // ок
}
Пример параметров:
class ParentClass
{
public function process(Dog $dog): void { }
}
class ChildClass extends ParentClass
{
public function process(Animal $animal): void { } // ок — шире
}
Сузить нельзя:
class ChildClass extends ParentClass
{
public function process(SpecificDog $dog): void { } // Error
}
Наследование от нескольких
⚠️ В PHP нет множественного наследования классов.
Один класс — один родитель:
class A {}
class B {}
class C extends A, B { } // Error
Множественное поведение — через трейты (следующий урок) или интерфейсы.
Иерархия
Многоуровневое наследование:
class A { }
class B extends A { }
class C extends B { }
class D extends C { }
Работает, но избегай глубоких иерархий — тяжело поддерживать.
Правило: максимум 2–3 уровня.
Абстрактные методы
В абстрактном классе:
abstract class Shape
{
abstract public function area(): float;
}
class Circle extends Shape
{
public function __construct(private float $radius) {}
public function area(): float
{
return M_PI * $this->radius ** 2;
}
}
Наследник обязан реализовать.
Полиморфизм
Один интерфейс, разное поведение:
abstract class Shape
{
abstract public function area(): float;
}
class Circle extends Shape
{
public function __construct(private float $r) {}
public function area(): float { return M_PI * $this->r ** 2; }
}
class Square extends Shape
{
public function __construct(private float $side) {}
public function area(): float { return $this->side ** 2; }
}
function totalArea(array $shapes): float
{
$total = 0.0;
foreach ($shapes as $shape) {
$total += $shape->area(); // вызов нужного метода
}
return $total;
}
$shapes = [new Circle(1.0), new Square(2.0)];
echo totalArea($shapes); // ~7.14
Полиморфизм — ключевая сила ООП.
final
Запретить наследование:
final class User { }
class Admin extends User { } // Error
Или метод:
class Foo
{
final public function bar(): void { }
}
class Child extends Foo
{
public function bar(): void { } // Error
}
Правило: final по умолчанию, открывай осознанно.
parent:: в разных местах
Только в наследнике:
class Child extends Parent
{
public function method(): void
{
parent::method(); // ok
}
}
parent — статический контекст.
static:: vs self::
self:: — текущий класс в коде:
class Foo
{
public static function name(): string
{
return self::class; // всегда Foo
}
}
static:: — вызывающий класс:
class Base
{
public static function name(): string
{
return static::class;
}
}
class Child extends Base {}
echo Child::name(); // Child
Позднее статическое связывание.
Шаблонный метод
Классический паттерн:
abstract class ReportGenerator
{
final public function generate(): string
{
$data = $this->getData();
$html = $this->render($data);
return $this->wrap($html);
}
abstract protected function getData(): array;
abstract protected function render(array $data): string;
private function wrap(string $html): string
{
return "<html><body>$html</body></html>";
}
}
class SalesReport extends ReportGenerator
{
protected function getData(): array
{
return ['sales' => 100];
}
protected function render(array $data): string
{
return "<h1>Sales: {$data['sales']}</h1>";
}
}
Алгоритм в родителе, шаги — в потомках.
Sealed classes (будущее)
В PHP нет sealed, но есть final и enum.
Практика: Notification
<?php
declare(strict_types=1);
abstract class Notification
{
public function __construct(
protected readonly string $message,
) {}
public function getMessage(): string
{
return $this->message;
}
abstract public function send(string $to): string;
}
final class EmailNotification extends Notification
{
public function send(string $to): string
{
return "Email на $to: {$this->message}";
}
}
final class SmsNotification extends Notification
{
public function send(string $to): string
{
return "SMS на $to: " . mb_substr($this->message, 0, 70);
}
}
final class PushNotification extends Notification
{
public function send(string $to): string
{
return "Push для $to: {$this->message}";
}
}
$notifications = [
new EmailNotification('Привет'),
new SmsNotification('Длинное сообщение...'),
new PushNotification('Уведомление'),
];
foreach ($notifications as $n) {
echo $n->send('+79001234567') . "\n";
}
Наследование vs композиция
«Favor composition over inheritance» — принцип ООП.
Наследование — «is-a»:
class Dog extends Animal { } // Dog is-a Animal
Композиция — «has-a»:
class Car
{
public function __construct(
private Engine $engine, // Car has-a Engine
) {}
}
Обычно композиция гибче.
Проблема наследования
Хрупкий базовый класс:
class Base
{
public function process(array $items): void
{
foreach ($items as $item) {
$this->beforeItem($item);
// ...
}
}
protected function beforeItem(mixed $item): void { }
}
Если потомок переопределит process — сломает логику. Или если родитель поменяет порядок вызовов — сломает потомков.
Композиция решает эту проблему.
Итог
extends— наследование.parent::— вызов родителя.- Переопределение — своя реализация.
final— запрет наследования.- Контравариантность параметров, ковариантность возврата.
- Полиморфизм — разное поведение через общий интерфейс.
- Композиция лучше наследования в большинстве случаев.
Дальше — abstract классы.