🧱 Суть
В 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 | |
|---|---|---|---|
| Что даёт | готовый класс | контракт | готовый код |
| Ключевое слово | extends | implements | use |
| Может содержать реализацию | ✅ | ❌ | ✅ |
| Количество | 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добавляет готовую реализацию в разные классы- интерфейсы чаще используются для архитектуры и слабой связанности
- трейты – для небольших повторяемых частей кода
- наследование нужно использовать осторожно, предпочитая композицию там, где нет отношения “является”