PHP поддерживает только одиночное наследование: класс может расширять лишь один родительский класс. Но что делать, если нужно поделить одну и ту же логику между несколькими совершенно разными классами? Именно для этого появились трейты — механизм повторного использования кода, который встраивается в любой класс без наследования.
Что такое трейт
Трейт — это набор методов и свойств, который объявляется ключевым словом trait и подключается в класс через use. PHP буквально «вставляет» код трейта в класс во время компиляции, как если бы эти методы были написаны в самом классе. Создать объект трейта напрямую нельзя — он всегда используется через класс.
Объявление и подключение трейта
Создадим трейт с логикой временных меток и подключим его к двум разным классам:
trait Timestampable
{
private ?\DateTimeImmutable $createdAt = null;
private ?\DateTimeImmutable $updatedAt = null;
public function touch(): void
{
$now = new \DateTimeImmutable();
$this->createdAt ??= $now;
$this->updatedAt = $now;
}
public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->createdAt;
}
public function getUpdatedAt(): ?\DateTimeImmutable
{
return $this->updatedAt;
}
}
// Подключаем трейт в разных классах через use
class Article
{
use Timestampable;
public function __construct(public string $title) {}
}
class User
{
use Timestampable;
public function __construct(public string $name) {}
}
$article = new Article('Введение в PHP');
$article->touch();
echo $article->getCreatedAt()->format('d.m.Y'); // 11.10.2026
$user = new User('Алексей');
$user->touch();
echo $user->getUpdatedAt()->format('H:i'); // текущее время
Метод touch() написан один раз в трейте, но работает и в Article, и в User. Внутри трейта $this указывает на объект того класса, в котором трейт используется.
Несколько трейтов в одном классе
Класс может подключать сразу несколько трейтов — перечислив их через запятую или отдельными строками use:
trait SoftDeletable
{
private bool $deleted = false;
public function delete(): void { $this->deleted = true; }
public function restore(): void { $this->deleted = false; }
public function isDeleted(): bool { return $this->deleted; }
}
trait Loggable
{
private array $logs = [];
public function log(string $message): void
{
$this->logs[] = '[' . date('H:i:s') . '] ' . $message;
}
public function getLogs(): array { return $this->logs; }
}
// Три трейта в одном классе
class Post
{
use Timestampable, SoftDeletable, Loggable;
public function __construct(public string $title) {}
}
$post = new Post('Трейты в PHP');
$post->touch();
$post->log('Пост создан');
$post->delete();
$post->log('Пост удалён');
var_dump($post->isDeleted()); // true
print_r($post->getLogs());
// [12:00:01] Пост создан
// [12:00:01] Пост удалён
Конфликт методов
Если два трейта объявляют метод с одним именем, PHP выбросит ошибку. Конфликт решается явно с помощью insteadof и as:
trait A { public function hello(): void { echo "Привет из A\n"; } }
trait B { public function hello(): void { echo "Привет из B\n"; } }
class MyClass
{
use A, B {
A::hello insteadof B; // использовать hello() из A
B::hello as helloFromB; // hello() из B — под другим именем
}
}
$obj = new MyClass();
$obj->hello(); // Привет из A
$obj->helloFromB(); // Привет из B
Трейты не решают задачу полиморфизма — проверка instanceof по трейту работать не будет. Если нужен контракт типа, используйте интерфейс. Трейты и интерфейсы хорошо дополняют друг друга: интерфейс задаёт контракт, трейт — реализацию.
Трейты — элегантный ответ PHP на ограничение одиночного наследования. Они позволяют выделить повторяющийся код в отдельный блок и подключать его к любым классам без создания искусственных иерархий. Логика временных меток, мягкого удаления, логирования — всё это отличные кандидаты для трейтов.
Главное правило: трейт должен решать одну конкретную задачу. Небольшой, сфокусированный трейт — хорошая практика. Трейт, который пытается сделать всё сразу, — признак неудачной архитектуры.
0 Комментарий(я)
Зарегистрируйтесь чтобы оставить комментарий