Menu

Трейты PHP: use, несколько трейтов и insteadof

Трейт PHP это переиспользуемый блок методов и свойств, который копируется в класс через use TraitName;. Несколько трейтов, разрешение конфликтов имён через insteadof и as, абстрактные и статические члены трейта, чей метод побеждает и сравнение трейта, интерфейса и абстрактного класса.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Трейт 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, хотя конструктор в трейте легко конфликтует с собственным конструктором класса и редко бывает хорошей идеей.

Чей метод побеждает, если класс и трейт определяют один и тот же метод?

Собственный метод класса побеждает метод трейта, а метод трейта побеждает метод, унаследованный от родительского класса. Так что порядок такой: класс, потом трейт, потом родитель.

Иллюстрация языков программирования Coddy

Учитесь программировать с Coddy

НАЧАТЬ