Трейт — механізм горизонтального повторного використання коду. Компілятор бере методи й властивості трейта та вставляє їх у клас так, ніби вони були написані в ньому. Клас може підключити кілька трейтів, і саме цим PHP компенсує відсутність множинного наслідування.
Важлива різниця з класами й інтерфейсами: трейт не є типом. По ньому не можна зробити type-hint, instanceof завжди поверне false, а сам трейт неможливо створити. Якщо потрібен контракт, трейт доповнюють інтерфейсом: інтерфейс обіцяє метод, трейт постачає реалізацію за замовчуванням.
Конфлікти вирішуються явно. Якщо два трейти мають однаковий метод, PHP не вгадує, а вимагає вказати insteadof, і за потреби дає доступ до другої реалізації через as. Той самий as змінює видимість методу.
Межа застосування проста: трейт добре працює як технічна домішка без власного стану, наприклад логування, timestamps, серіалізація. Коли в трейт починають потрапляти бізнес-правила й залежності на кшталт $this->db, код стає непрозорим: клас отримує поведінку, походження якої не видно в його оголошенні. У таких випадках краще композиція через окремий сервіс у конструкторі.
trait Loggable
{
// Трейт може вимагати від класу метод, не знаючи його реалізації
abstract protected function logger(): LoggerInterface;
public function log(string $message): void
{
$this->logger()->info(static::class.': '.$message);
}
}
trait Timestamps
{
public function touch(): void { $this->updatedAt = new DateTimeImmutable; }
}
final class Order
{
use Loggable, Timestamps {
Timestamps::touch as protected markUpdated; // псевдонім зі зміною видимості
}
private DateTimeImmutable $updatedAt;
public function __construct(private LoggerInterface $logger) {}
protected function logger(): LoggerInterface { return $this->logger; }
}
$order instanceof Loggable; // false: трейт не є типом
Хороша відповідь містить межу застосування: трейт доречний для технічної домішки без власного стану, а поведінку домену краще виносити в окремий сервіс. Наведіть приклад з Laravel: SoftDeletes або HasFactory.