Трейт PHP это переиспользуемый блок методов и свойств. Объявите его через trait Name { ... } и подключите в любой класс через use Name; внутри тела класса. PHP копирует члены трейта в класс, поэтому они работают ровно так, будто вы написали их там.
У User и Robot нет общего родителя, но теперь у обоих есть hello(). Трейты существуют именно для этого случая: классы PHP могут наследовать только от одного класса, а трейт делится кодом в сторону, между несвязанными классами.
Трейты со свойствами
Трейт может принести вместе с методами и свойства. Методы используют $this, как любой метод класса, потому что после копирования трейта они и есть методы класса:
Класс не может объявить свойство с тем же именем, что у свойства трейта, если только оба объявления не идентичны (та же видимость, тип и значение по умолчанию); иначе это фатальная ошибка.
Несколько трейтов
Перечислите несколько трейтов в одной инструкции use через запятую или напишите несколько строк use. Класс получит члены их всех:
Разрешение конфликтов через insteadof и as
Когда два трейта определяют метод с одним и тем же именем, PHP отказывается угадывать и останавливается с фатальной ошибкой. Внутри блока use insteadof выбирает победителя, а as сохраняет проигравший метод под другим именем:
С простым use EnglishGreeting, FrenchGreeting; без блока скрипт останавливается раньше, чем что-либо выведет:
PHP Fatal error: Trait method FrenchGreeting::greet has not been applied as Host::greet, because of collision with EnglishGreeting::greet in /home/index.php on line 12
as умеет и менять видимость. use Loggable { log as protected; } делает log() из трейта защищённым только в этом классе, а use Loggable { log as private writeLog; } добавляет закрытый псевдоним.
Абстрактные и статические члены трейта
Трейт может потребовать что-то от использующего его класса, объявив абстрактный метод. В нём также могут быть статические методы и статические свойства. Каждый класс, который использует трейт, получает свою копию статического свойства:
Car и Bike считают отдельно, потому что статическое свойство копируется в каждый класс. Это не так, как со статическим свойством в общем родительском классе, которое разделяют все потомки; этот случай показан на странице о static.
Чей метод побеждает: класс, трейт или родитель
Если метод с одним именем есть в самом классе, в используемом им трейте и в родительском классе, PHP выбирает в таком порядке: собственный метод класса, потом метод трейта, потом метод родителя. Внутри трейта parent:: по-прежнему достаёт версию родителя:
Трейт это не тип
instanceof никогда не совпадает с трейтом, а параметр с типом в виде имени трейта отклоняет любой объект с TypeError. Чтобы спросить "есть ли у этого класса такое поведение", соедините трейт с интерфейсом или проверьте класс через class_uses():
Шаблон выше обычный: интерфейс Exportable это то, что указывают функции в подсказках типа и что проверяет instanceof, а трейт ExportsProperties это готовая реализация, которую классы могут использовать или заменить. Трейт сам по себе это общий код без контракта; если вы замечаете, что проверяете class_uses() в бизнес-логике, трейту нужен был интерфейс рядом. Для кода, общего для семейства связанных классов, проще может оказаться абстрактный класс.
Часто задаваемые вопросы
Что такое трейт в PHP?
Трейт это именованная группа методов (и свойств), объявленная через trait Name { }, которую класс подключает через use Name;. PHP копирует члены трейта в класс, поэтому они ведут себя так, будто вы написали их там. Это способ поделиться кодом между классами, у которых нет общего родителя.
Может ли класс PHP использовать несколько трейтов?
Да: use Timestamps, SoftDeletes; или несколько строк use. Если два трейта определяют метод с одним и тем же именем, нужно выбрать один через insteadof, например use A, B { A::hello insteadof B; }, а второй можно сохранить под новым именем через B::hello as helloB;.
Чем трейт отличается от интерфейса в PHP?
Интерфейс это тип без кода: он говорит, какие методы должны быть у класса, и с ним работает instanceof. Трейт это код без типа: он вставляет тела методов в класс, но $obj instanceof SomeTrait всегда ложно. Их часто сочетают: интерфейс для типа, трейт с реализацией по умолчанию.
Может ли у трейта быть конструктор или абстрактные методы?
Да, и то, и другое. Трейт может объявить abstract public function name(): string;, чтобы потребовать этот метод от использующего класса, и может определить __construct, хотя конструктор в трейте легко конфликтует с собственным конструктором класса и редко бывает хорошей идеей.
Чей метод побеждает, если класс и трейт определяют один и тот же метод?
Собственный метод класса побеждает метод трейта, а метод трейта побеждает метод, унаследованный от родительского класса. Так что порядок такой: класс, потом трейт, потом родитель.