php трейты зачем нужны
Трейты в PHP
PHP поддерживает только одиночное наследование: дочерний класс не может наследовать от нескольких классов сразу, только от одного единственного родителя. Однако в большинстве случаев было бы полезно наследовать от нескольких классов. Например, было бы желательно наследовать методы от нескольких разных классов, чтобы предотвратить дублирование кода. Трейты используются для восполнения этого пробела, позволяя нам повторно использовать одни и те же свойства и методы в нескольких классах.
Что такое трейты?
Трейты (англ. trait) используются для объявления методов, которые можно использовать в нескольких классах. Трейты могут иметь методы и абстрактные методы, которые могут использоваться в нескольких классах. Методы трейтов могут иметь любой модификатор доступа (публичный, приватный или защищенный).
Синтаксис трейта такой же как и у класса, за исключением того, что имя трейта нужно объявлять с помощью ключевого слова trait :
Синтаксис
Экземпляр трейта, как и абстрактного класса, нельзя создать — трейты предназначены только для подключения к другим классам.
Синтаксис
Давайте посмотрим на пример в котором объявим один трейт и подключим к классу:
Пример
Результат выполнения кода:
По сути, трейт — это просто способ скопировать и вставить код во время выполнения.
Использование нескольких трейтов
Пример
Результат выполнения кода:
Чем трейты отличаются от интерфейсов?
Трейты очень похожи на интерфейсы. И трейты, и интерфейсы обычно просты, лаконичны и мало используются без реально реализованного класса. Однако разница между ними есть.
Интерфейс — это контракт, в котором говорится, что «этот объект может делать это», тогда как трейт дает объекту возможность делать это.
Другими словами, если код ООП касается планирования и проектирования, то интерфейс — это план, а объект — полностью построенный дом. Между тем, трейты — это просто способ помочь построить дом, спроектированный по плану (интерфейсу).
Интерфейсы — это спецификации, которые можно проверить используя оператор instanceof (является ли текущий объект экземпляром указанного класса).
Вы должны использовать трейты только тогда, когда несколько классов имеют одну и ту же функциональность (вероятно, продиктованную одним и тем же интерфейсом). Нет смысла использовать трейт для обеспечения функциональности для одного класса: это только запутывает то, что делает класс.
Пример
Результат выполнения кода:
Основное отличие состоит в том, что с интерфейсами вы должны определить фактическую реализацию каждого метода в каждом классе, реализующем указанный интерфейс, поэтому вы можете иметь множество классов, реализующих один и тот же интерфейс, но с различным поведением. В то время как трейты — это просто фрагменты кода, введенные в класс. Еще одно важное отличие состоит в том, что методы трейтов могут быть только методами класса или статическими методами, в отличие от методов интерфейса, которые также могут (и обычно являются) методами экземпляра.
Переопределение унаследованных методов
Как описано в Руководстве: Унаследованный метод от базового класса переопределяется методом, вставленным с помощью трейта. Порядок приоритета таков — методы текущего класса переопределяют методы трейта, которые в свою очередь переопределяют унаследованные методы.
Итак, рассмотрим следующий сценарий:
Пример
Результат выполнения кода:
При создании экземпляра MyClass, описанного выше, происходит следующее:
Заключение
Прочитал про новую штуку в php trait
Сделал первый пример
Работа понятна, вопрос чем отличается вышепоказанный пример, от
Они нужны для избавления от дублирования кода, ну или например для множественного наследования.
При таком подходе вы всегда класс логгера можете заменить, просто отредактировав всего один файл трейта, а не 100500 классов.
Еще пример:
В трейт можно вынести функцию isAjaxPost, для проверки, что запрос в контроллер пришел ajax post, и подключать в нужные контроллеры.
Да вообщем море примеров привести можно, где это удобно применять, для избавления от дублирование в коде, от однотипных операций и т.д.
@Fesor всмысле когда, например модуль Блог хочет написать личное сообщение пользователю (Тесная связь модуля Блог и модуля Сообщения).
Берем модуле блог делаем трейт, который знает про модуль сообщения и везде в модуле блог работает с трейтом.
@Fesor Не соглашусь насчет макроса и того, что код вставляется в место use TraitName, в PHP это реализовано, почти так же, как extend, за исключением приоритетов, кто кого перекрывает. ну и да одноименные функции из трейтов друг друга перекрыть не могут, будет ошибка, но есть механизм выбора.
Насчет множественного наследования, это было в качестве примера, множественное наследование вообще штука не очень хорошая, но при грамотном и острожном использование пойдет 🙂
@nepster09 Сходу так прям не ответить, всего скорей это не удачное решение, т.к получаем связь блога и сообщений, с другой стороны если система не сложна, то вполне себе решение.
Если делать через трейты, я бы наверное сделал следующем образом, трейт отвечал бы за получение экземпляра конкретного модуля, ну через какой-нить контейнер зависимостей, подключал в него все зависимости т.д.
trait UserModuleTrait
<
protected function getUserModule() < // логика создания >
>
В модели блога подключал бы этот трейт, и через него получал связь с модулем пользователей, а через модуль пользователей на модуль сообщений и отправлял сообщение бы.
Но тут получается такая страшная штука, если условия отправления сообщений сменить с 10, на к примеру каждого 3 отправленное в субботу утром, то получается постоянно лезть в модель блога для исправления.
Всего скорей тут лучше сделать через эйвент по факту создания новой записи, это эйвент отлавливать и уже в обработчики реализовать всю логику по отправки сообщений. А сами связи на модуле получать через контейнер сервисов, ну так будет правильнее.
Хотя еще раз повторюсь, если проект не сложный, то первый вариант вполне себе рабочее решение 🙂
Трейты в PHP
Трейты используются когда нужно реализовать ряд общих методов для нескольких классов, не присваивая экземплярам класса новый тип, как при использовании абстрактных классов или интерфейсов.
Трейты решают проблему дублирования кода.
Давайте рассмотрим пример использования трейта.
Важно понимать то, что трейты не присваивают экземплярам класса (объектам) новый тип.
Невозможно создать самостоятельный экземпляр трейта.
Использование нескольких трейтов
С этим моментом всё просто.
Совместное использование трейтов и интерфейсов
Конфликты имён в трейтах, insteadof.
Давайте на примере рассмотрим как это работает:
Псевдонимы для переопределённых методов
Вот как это работает:
Статические методы в трейте
Нам ничего не мешает объявлять методы в трейте статическими. Давайте рассмотрим пример трейта со статическими методами.
Я и в прошлых примерах обращался к методу трейта напрямую, без создания объекта. В этом примере я только лишь объявил метод трейта как статический.
Доступ к свойствам базового класса
Абстрактные методы в классах
Если в трейте объявляется абстрактный метод, то этот метод должен быть реализован в базовом классе.
В следующем примере мы гарантируем наличие свойства в базовом классе объявив в трейте абстрактный метод.
Изменения прав доступа к методам трейта
Очевидно, что внутри трейта мы можем использовать любой любой модификатор доступа ( public, private, protected ) для метода. Но, кроме этого, у нас есть возможность в классе менять этот модификатор на другой. Для этого в оператор use после слова as можно указать новый модификатор.
Итоги
Кратко о том, что мы выучили.
Трейты в PHP
Нужные для использования в классе трейты можно указать через запятую:
Приоритет методов при работе с трейтами.
— члены из текущего класса переопределяют одноименные методы в трейте,
— члены из трейта переопределяют унаследованные классом методы. То есть трейт имеет преимущество перед классом который наследуем.
Конфликты трейтов.
Ошибки могут быть когда подключается несколько трейтов, содержащие одни и те же методы. Или когда класс наследует у другого класса с подключением трейта, который уже был подключен в родительском классе.
Для разрешения конфликтов необходимо использовать оператор insteadof при подключении трейтов для того, чтобы точно выбрать один из конфликтных методов.
Внутри тела «use» мы использовали ключевое слово insteadof, слева от которого указывается трейт, метод которого будем использовать и имя самого метода, которые разделяются двойным двоеточием. В правой части указывается имя трейта, метод которого должен быть заменён.
Если же второй одноименный метод (из другого трейта) нам тоже нужен, то можно применить псевдоним имени используя ключевое слово as :
Статические методы и свойства
Используются так же как и в классах:
Доступ к свойствам базового класса.
В трейтах для доступа к свойствам базового класса можно использовать псевдопеременную $this.
Изменения прав доступа к методам трейта.
Внутри трейта мы можем использовать любой модификатор доступа (public, private, protected) для методов. Но, кроме этого, есть возможность в классе менять этот модификатор на другой. Для этого в теле use после слова as можно указать новый модификатор.
Traits в php 5.4. Разбираем детали реализации
Совсем недавно вышла первая beta php 5.4, а пока я писал топик подоспела и вторая. Одно из нововведений в 5.4 – это traits (типажи). Предлагаю разобраться во всех деталях в том, что же типажи из себя представляют в php.
Но во всём есть свои детали.
Синтаксис
В общем и целом всё просто. Типажей можно подключить к классу неограниченное кол-во через одну или несколько конструкций use внутри определения класса. use может быть указан в любом месте класса.
Более сложный пример:
Тут важно обратить внимание на два момента. Во-первых, блок после use кажется связанным с типажом около которого он описан, но это не так. Правила в блоке глобальные и могут быть объявлены в любом месте.
Типажи инициализируются, как и классы, динамически. При большом желании можно писать так:
Свойства в типажах
Область видимости
Статические методы и свойства
В типаже можно объявлять статические методы, но нельзя объявлять статические свойства. Внутри статических методов можно использовать, как статическое связывание (self::), так и динамическое (static::), всё будет работать так, как будто вызвано из метода класса («copy-paste»).
Ограничение на хранение статических свойств обойти можно, как именно покажу позже с обращением к магии.
Совпадение методов типажей между собой и с методами класса
Метод описанный в классе перекрывает метод из типажа. Но если какой-то метод описан в родительском классе, а в дочернем классе подключён типаж с таким же методом, он перекроет метод из родительского (снова вспоминаем «copy-paste»).
Хитрая ошибка может быть в случае, когда в классе тоже определён метод, вызвавший коллизию, в таком случае php пропустит эту проверку, т.к. он проверяет только «выжившие» методы типажа:Когда-нибудь потом, перенеся метод abc в родительский класс, получим странную ошибку по коллизии методов типажей, которая может сбить с толку. Так что, коллизии лучше разрешить заранее. (С другой стороны, если в коде методы типажа и класса совпадают, возможно что-то уже не так.)
Совпадение свойств типажа со свойствами другого типажа и свойствами класса
В этом моменте нас поджидают неприятные проблемы. Сразу пример:
Поясняю. В общем случае при пересечении свойств типажей между собой или свойств типажа и класса выдаётся ошибка. Но зачем-то для «совместимых» свойств делается исключение и они работают по принципу «кто последний, тот и прав». Поэтому в классе A в getId получилось NULL, а в классе B – false. При этом свойства класса считаются ниже, чем свойство типажа (с методами равно наоборот) и в C вместо ожидаемого ‘0’ получим false.
Совместимыми считаются значения нестрогое сравнение которых даёт true, а так как в php при этом много неявных преобразований, могут быть неприятные ошибки при использовании строго сравнения возвращаемых значений.
Так что практика с префиксами, предложенная выше, будет полезна и в таких случаях. Я же надеюсь что эту часть реализации ещё пересмотрят к релизу.
Ошибки и исключения в типажах
Если следовать мнемоническому правилу trait == «copy-paste», с ошибками становится сразу всё понятно:
Объект уже не знает, откуда у него взялся метод в котором был Notice или Exception, но это можно узнать в stack trace по строкам кода, в которых были вызовы. Если хранить типажи в отдельных файлах определить будет ещё проще.
Немного белой чёрной магии
Покажу пару грязных приёмов с типажами, используйте их на свой страх и риск.
Удаление метода типажа
Чтобы удалить метод типажа, например, когда ему был задан alias, можно сделать так:
Но в таком подходе таится большая опасность, т.к. одни методы типажа потенциально могут вызывать другие методы:
При переименовании типаж ничего не знает о том, что метод был переименован. Поэтому по-умолчанию при указании alias’а сохраняется оригинальный метод.
«Наследование» в типажах
С помощью похожего трюка можно реализовать «наследование» в типажах c возможностью вызова «родительских» методов.
Два способа реализовать Singleton с помощью типажей
Чтобы сгладить это магическое безобразие покажу один полезный пример. Часто в виде типажа приводят Singleton, хотя без возможности задания в типаже статической переменной сделать его будет не так просто, как кажется на первый взгляд. Можно воспользоваться двумя хитростями.
Первая – получить внутри вызываемого метода название класса, к которому он был вызван, а затем в качестве хранилища воспользоваться отдельным классом со статическим методом, примерно так:
Вторая – воспользоваться толи фичей, толи багой php, которая связана с использованием ключевого слова static при объявлении переменной. Эти переменные должны сохранять своё значение при вызовах метода, но видимо структура для хранения этих переменных инициализируется в каждом месте использования метода. В итоге получается такая схема: