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

«Почему нельзя всегда использовать трейты вместо наследования в PHP» — вопрос из категории PHP Core, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Ключевые ограничения трейтов:

  1. Нет полиморфизма через трейты

    trait Loggable {
       public function log($message) { /* ... */ }
    }
    
    // Нельзя type-hint по трейту
    function process(Loggable $object) { // Ошибка!
       $object->log('test');
    }
  2. Конфликты имён при множественном использовании

    trait A { public function process() { return 'A'; } }
    trait B { public function process() { return 'B'; } }
    
    class MyClass {
       use A, B; // Fatal error!
    
       // Нужно явно разрешать конфликт
       use A, B {
           A::process insteadof B;
           B::process as processB;
       }
    }
  3. Нарушение инкапсуляции — трейты могут инжектить публичные методы и свойства, нарушая контроль класса.

  4. Сложность отладки — при использовании многих трейтов сложно отследить, откуда пришёл метод.

Правильное разделение:

  • Наследование для отношений «является» (например, User → AdminUser)
  • Трейты для общих поведений (например, SoftDeletes, Timestampable)

Пример из моего опыта: в Laravel-проекте мы использовали трейт Notifiable для отправки уведомлений, но наследование от Model определяло базовую сущность.