🧱 Суть

В PHP есть несколько способов переиспользовать и расширять код:

  • наследование (extends) – создание нового класса на основе существующего
  • интерфейсы (implements) – описание обязательного набора методов
  • трейты (trait) – добавление готовой реализации методов в разные классы

Они решают разные задачи.

Пример:

class Admin extends User implements AuthInterface
{
    use LoggerTrait;
}

Здесь:

  • Admin наследует User
  • реализует контракт AuthInterface
  • подключает готовый код из LoggerTrait

📦 Проблема

1. Дублирование кода

Без повторного использования приходится копировать одинаковую логику:

class User
{
    public function log()
    {
        echo "save log";
    }
}
 
class Product
{
    public function log()
    {
        echo "save log";
    }
}

Если изменить логику логирования – нужно менять несколько мест.

2. Слишком большие классы

Со временем один класс может начать отвечать за всё:

class User
{
    // авторизация
    // логирование
    // отправка email
    // работа с БД
}

Такой код сложно поддерживать.

3. Нет единого контракта

Иногда разные классы должны уметь одно и то же:

Например:

  • разные способы оплаты
  • разные хранилища данных
  • разные уведомления
    Но реализация у них разная.

✅ Решение

Использовать подходящий механизм:

ЗадачаИнструмент
Создать похожий классextends
Задать обязательные методыinterface
Добавить общую реализациюtrait

💻 Код

Наследование (extends)

Наследование позволяет одному классу получить свойства и методы другого.

Пример:

class User
{
    public function login()
    {
        echo "Login";
    }
}
 
class Admin extends User
{
}

Теперь:

$admin = new Admin();
 
$admin->login();

Метод был унаследован от User.

Добавление своих методов

Дочерний класс может расширять родителя:

class Admin extends User
{
    public function deleteUser()
    {
        echo "Удаление пользователя";
    }
}

Теперь:

$admin->login();
$admin->deleteUser();

Переопределение методов

Дочерний класс может изменить поведение:

class Admin extends User
{
    public function login()
    {
        echo "Admin login";
    }
}

Теперь:

$admin->login();

выведет:

Admin login

Вызов родительского метода (parent)

Можно обратиться к версии родителя:

class Admin extends User
{
    public function login()
    {
        parent::login();
 
        echo "Admin actions";
    }
}

Ограничения наследования

PHP поддерживает только одиночное наследование:

Можно:

class Admin extends User
{
}

Нельзя:

class Admin extends User, Account
{
}

Один класс может наследовать только один другой класс.

Интерфейсы (interface)

Интерфейс – это контракт, который говорит:

“Класс обязан иметь такие методы”

Пример:

interface Payment
{
    public function pay(float $amount);
}

Интерфейс не содержит основной логики.

Реализация интерфейса

Класс использует implements:

class CardPayment implements Payment
{
    public function pay(float $amount)
    {
        echo "Оплата картой";
    }
}

Теперь класс обязан иметь метод:

pay()

Несколько интерфейсов

В отличие от классов, интерфейсов может быть несколько:

class UserService implements
    AuthInterface,
    CacheInterface
{
}

Зачем нужны интерфейсы

Они позволяют работать не с конкретным классом, а с возможностью.

Плохо:

function checkout(CardPayment $payment)
{
}

Функция принимает только карту.

Лучше:

function checkout(Payment $payment)
{
}

Теперь подходят:

CardPayment
CashPayment
CryptoPayment

Любой класс, который реализует Payment.

Полиморфизм через интерфейсы

Пример:

interface Notification
{
    public function send(string $message);
}

Реализации:

class EmailNotification implements Notification
{
    public function send(string $message)
    {
        echo "Email: $message";
    }
}
 
 
class SmsNotification implements Notification
{
    public function send(string $message)
    {
        echo "SMS: $message";
    }
}

Использование:

function notify(Notification $service)
{
    $service->send("Hello");
}

Теперь функция работает с любым способом отправки.

Трейты (trait)

Trait – механизм повторного использования готового кода между классами.
Он похож на “кусок класса”, который вставляется внутрь другого.

Пример:

trait LoggerTrait
{
    public function log(string $message)
    {
        echo $message;
    }
}

Использование:

class UserService
{
    use LoggerTrait;
}

Теперь:

$service->log("Ошибка");

работает автоматически.

Trait с несколькими классами

Один trait можно использовать повторно:

class UserController
{
    use LoggerTrait;
}
 
 
class ProductController
{
    use LoggerTrait;
}

Оба класса получили метод log().

Несколько трейтов

Можно подключить несколько:

class User
{
    use LoggerTrait, CacheTrait;
}

Конфликт методов в Trait

Если два трейта имеют одинаковый метод:

trait A
{
    public function save(){}
}
 
trait B
{
    public function save(){}
}

Возникнет конфликт:

class User
{
    use A, B;
}

Решение:

class User
{
    use A, B {
        A::save insteadof B;
    }
}

Наследование vs интерфейс vs trait

НаследованиеИнтерфейсTrait
Что даётготовый классконтрактготовый код
Ключевое словоextendsimplementsuse
Может содержать реализацию
Количество1 родительмного интерфейсовмного трейтов
Главная цельсвязь “является”общий контрактпереиспользование

Как выбирать

Используй наследование, когда есть настоящая связь:

Admin → User
Dog → Animal

То есть:

“Admin является User”

Используй интерфейс, когда нужно описать способность:

Payment
Logger
Cacheable

То есть:

“Объект умеет делать это”

Используй trait, когда нужен общий кусок реализации:

LoggerTrait
TimestampTrait
UuidTrait

Частые ошибки

Злоупотребление наследованием

Плохо:

class UserController extends Database
{
}

Контроллер не является базой данных.

Лучше:

class UserController
{
    private Database $db;
}

Использование trait вместо архитектуры

Плохо:

trait EverythingTrait
{
    // 500 строк логики
}

Trait не должен заменять нормальное разделение классов.

Интерфейс без необходимости

Не стоит создавать интерфейс для каждого класса:

UserInterface
UserServiceInterface

если он не даёт реальной пользы.

🎯 Вывод

  • extends используется для наследования классов
  • наследование создаёт связь между похожими объектами
  • interface задаёт обязательный контракт поведения
  • один класс может реализовать несколько интерфейсов
  • trait добавляет готовую реализацию в разные классы
  • интерфейсы чаще используются для архитектуры и слабой связанности
  • трейты – для небольших повторяемых частей кода
  • наследование нужно использовать осторожно, предпочитая композицию там, где нет отношения “является”

🔗 Связано