Трейты php когда использовать

Трейты в PHP

Как мы знаем, в PHP класс может наследоваться только от одного класса. Но как быть, если мы хотим иметь какой-либо функционал в разных классах, которые не являются наследниками друг друга? Для этого придумали трейты. Трейты в PHP – это такой механизм, который позволяет внутри классов избегать повторного использования кода.

Давайте разберём на примере. Пусть у нас будут два совершенно не связанных между собой класса: коробка и человек. Но предположим, что мы хотели бы, чтобы они оба умели говорить о том, какого они класса. Давайте для начала создадим два этих класса и прямо в них создадим методы sayYourClass(), которые будут выводить имя класса, объектами которого они являются.

В PHP можно получить имя класса с помощью конструкции ИмяКласса::class. Например:

Если мы находимся внутри класса, например, в каком-то его методе, то мы можем ИмяКласса заменить словом self – текущий класс.

Результат останется прежним.

Как видим, в классах получились одинаковые методы, и было бы неплохо избавиться от дублирования. Вот тут нам и понадобится трейт. Описываются трейты следующим образом:

Ничего сложного. Теперь мы можем просто использовать этот трейт в двух наших классах. Для этого используется конструкция use.

И снова увидим нужный результат:

Код из трейта SayYourClass просто подставился в классы, где мы его использовали с помощью слова use. В self будет лежать класс, в котором сейчас исполняется этот код. Вот так всё просто.

Трейты также довольно плотно пересекаются с темой интерфейсов.

Давайте добавим интерфейс, который будет обязывать классы иметь метод sayYourClass().

Теперь наши классы могут его реализовать – этот метод реализован в трейте, а трейт используется в классе.

Давайте посмотрим на получившийся код.

В PHP класс может наследоваться только от одного класса, помните? Так вот с помощью интерфейсов и трейтов мы можем это ограничение немного обойти. Мы теперь можем добавлять некоторый функционал в классы, которые не имеют какого-то общего поведения в целом. Но они при этом объединены одним интерфейсом. А проверять, реализует ли объект интерфейс, мы уже умеем – с помощью конструкции instanceof.

Таким образом, мы можем обходить ограничения наследования:

Источник

Трейты в PHP

Трейты используются когда нужно реализовать ряд общих методов для нескольких классов, не присваивая экземплярам класса новый тип, как при использовании абстрактных классов или интерфейсов.

Трейты решают проблему дублирования кода.

Давайте рассмотрим пример использования трейта.

Важно понимать то, что трейты не присваивают экземплярам класса (объектам) новый тип.

Невозможно создать самостоятельный экземпляр трейта.

Использование нескольких трейтов

С этим моментом всё просто.

Совместное использование трейтов и интерфейсов

Конфликты имён в трейтах, insteadof.

Давайте на примере рассмотрим как это работает:

Псевдонимы для переопределённых методов

Вот как это работает:

Статические методы в трейте

Нам ничего не мешает объявлять методы в трейте статическими. Давайте рассмотрим пример трейта со статическими методами.

Я и в прошлых примерах обращался к методу трейта напрямую, без создания объекта. В этом примере я только лишь объявил метод трейта как статический.

Доступ к свойствам базового класса

Абстрактные методы в классах

Если в трейте объявляется абстрактный метод, то этот метод должен быть реализован в базовом классе.

В следующем примере мы гарантируем наличие свойства в базовом классе объявив в трейте абстрактный метод.

Изменения прав доступа к методам трейта

Очевидно, что внутри трейта мы можем использовать любой любой модификатор доступа ( public, private, protected ) для метода. Но, кроме этого, у нас есть возможность в классе менять этот модификатор на другой. Для этого в оператор use после слова as можно указать новый модификатор.

Итоги

Кратко о том, что мы выучили.

Источник

Прочитал про новую штуку в php trait

Сделал первый пример

Работа понятна, вопрос чем отличается вышепоказанный пример, от

Они нужны для избавления от дублирования кода, ну или например для множественного наследования.

При таком подходе вы всегда класс логгера можете заменить, просто отредактировав всего один файл трейта, а не 100500 классов.

Еще пример:
В трейт можно вынести функцию isAjaxPost, для проверки, что запрос в контроллер пришел ajax post, и подключать в нужные контроллеры.

Да вообщем море примеров привести можно, где это удобно применять, для избавления от дублирование в коде, от однотипных операций и т.д.

@Fesor всмысле когда, например модуль Блог хочет написать личное сообщение пользователю (Тесная связь модуля Блог и модуля Сообщения).

Читайте также:  Что хранит сим карта

Берем модуле блог делаем трейт, который знает про модуль сообщения и везде в модуле блог работает с трейтом.

@Fesor Не соглашусь насчет макроса и того, что код вставляется в место use TraitName, в PHP это реализовано, почти так же, как extend, за исключением приоритетов, кто кого перекрывает. ну и да одноименные функции из трейтов друг друга перекрыть не могут, будет ошибка, но есть механизм выбора.

Насчет множественного наследования, это было в качестве примера, множественное наследование вообще штука не очень хорошая, но при грамотном и острожном использование пойдет 🙂

@nepster09 Сходу так прям не ответить, всего скорей это не удачное решение, т.к получаем связь блога и сообщений, с другой стороны если система не сложна, то вполне себе решение.

Если делать через трейты, я бы наверное сделал следующем образом, трейт отвечал бы за получение экземпляра конкретного модуля, ну через какой-нить контейнер зависимостей, подключал в него все зависимости т.д.

trait UserModuleTrait
<
protected function getUserModule() < // логика создания >
>

В модели блога подключал бы этот трейт, и через него получал связь с модулем пользователей, а через модуль пользователей на модуль сообщений и отправлял сообщение бы.

Но тут получается такая страшная штука, если условия отправления сообщений сменить с 10, на к примеру каждого 3 отправленное в субботу утром, то получается постоянно лезть в модель блога для исправления.

Всего скорей тут лучше сделать через эйвент по факту создания новой записи, это эйвент отлавливать и уже в обработчики реализовать всю логику по отправки сообщений. А сами связи на модуле получать через контейнер сервисов, ну так будет правильнее.

Хотя еще раз повторюсь, если проект не сложный, то первый вариант вполне себе рабочее решение 🙂

Источник

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.

Читайте также:  что нам принесет 21 век

Совместимыми считаются значения нестрогое сравнение которых даёт true, а так как в php при этом много неявных преобразований, могут быть неприятные ошибки при использовании строго сравнения возвращаемых значений.

Так что практика с префиксами, предложенная выше, будет полезна и в таких случаях. Я же надеюсь что эту часть реализации ещё пересмотрят к релизу.

Ошибки и исключения в типажах

Если следовать мнемоническому правилу trait == «copy-paste», с ошибками становится сразу всё понятно:
Объект уже не знает, откуда у него взялся метод в котором был Notice или Exception, но это можно узнать в stack trace по строкам кода, в которых были вызовы. Если хранить типажи в отдельных файлах определить будет ещё проще.

Немного белой чёрной магии

Покажу пару грязных приёмов с типажами, используйте их на свой страх и риск.

Удаление метода типажа

Чтобы удалить метод типажа, например, когда ему был задан alias, можно сделать так:
Но в таком подходе таится большая опасность, т.к. одни методы типажа потенциально могут вызывать другие методы:
При переименовании типаж ничего не знает о том, что метод был переименован. Поэтому по-умолчанию при указании alias’а сохраняется оригинальный метод.

«Наследование» в типажах

С помощью похожего трюка можно реализовать «наследование» в типажах c возможностью вызова «родительских» методов.

Два способа реализовать Singleton с помощью типажей

Чтобы сгладить это магическое безобразие покажу один полезный пример. Часто в виде типажа приводят Singleton, хотя без возможности задания в типаже статической переменной сделать его будет не так просто, как кажется на первый взгляд. Можно воспользоваться двумя хитростями.

Первая – получить внутри вызываемого метода название класса, к которому он был вызван, а затем в качестве хранилища воспользоваться отдельным классом со статическим методом, примерно так:
Вторая – воспользоваться толи фичей, толи багой php, которая связана с использованием ключевого слова static при объявлении переменной. Эти переменные должны сохранять своё значение при вызовах метода, но видимо структура для хранения этих переменных инициализируется в каждом месте использования метода. В итоге получается такая схема:

Источник

Как я использую трейты

Недавно несколько человек просили меня рассказать об использовании трейтов в новом проекте, который я пишу. Примерно в тоже время Рафаэль Домс показал мне его новую речь о сложных когнитивных процессах, которые мы не замечаем. Так как наш мозг — это большой мешок, перемешивающий все, в результате получился пост, который пытается охватить и то как я использую трейты, и то как я решаю где они нужны.

Воздействие vs Абстракция

Первое, что вы должны сделать — пойти почитать пост “Abstraction or Leverage” от Майкла Найгарда. Это отличная статья.

Если же у вас мало времени, основная суть поста состоит в том, что части кода (функции, классы, методы и т.д.) могут предназначаться либо для абстракции, либо для воздействия. Разница в:

Общим воздействием будет что-то вроде базового класса контроллера в вашем фреймворке. Он не скрывает реализацию, просто добавляет некоторые классные фичи, облегчающие работу.

В вышеупомянутом посте говорится, что как абстракции, так и воздействия хороши. Но абстракция — немного лучше, потому что она всегда дает вам возможность воздействовать, а воздействие не дает вам абстракции. Тем не менее, я хотел бы добавить, что хорошая абстракция является более трудозатратной в создании и не на всех уровнях возможна. Так что это компромисс.

Как это связано с трейтами?

Некоторые возможности языка лучше чем другие в создании либо воздействия, либо абстракции. Интерфейсы, например, отлично помогают нам строить и применять абстракции.

Наследование, с другой стороны, великолепно в предоставлении воздействия. Оно позволяет нам переопределить части родительского кода без необходимости копировать или извлекать каждый метод, использовать код класса (но не обязательно абстрактного) несколько раз. Так, отвечая на первоначальный вопрос, когда же я могу использовать трейты?

Я использую трейты, когда я хочу создать воздействие, не абстракцию.

Иногда?

Бенджамин Эберлей высказал хороший аргумент, что трейты имеют в основном те же проблемы, что и статический доступ. Вы не можете заменить или переопределить их, они откровенно плохо поддаются тестированию.

Но все же статические методы полезны. Если у вас одна функция без состояния и вы не хотите заменить ее на другую реализацию, то нет ничего плохого в том, чтобы сделать ее статической. Именованные конструкторы (вы же редко хотите именно пустой объект) или получение массива/результата математических операций с хорошо определенными вводом/выводом, без состояния, детерминированные: все это вам интересно. Статическое состояние, а не методы, вот реальное зло.

Читайте также:  Что требуется для переливания крови

Трейты имеют примерно те же ограничения, плюс они могут быть использовании только внутри класса. Они более глобальны, чем объект.

Это дает трейтам дополнительную особенность: они могут работать (читать и писать) с внутренним состоянием класса, в который подмешаны. В некоторых случаях это делает их более подходящими чем статические методы.

Например, я часто использую генерацию доменных событий в сущности:

Мы можем сделать рефакторинг и превратить этот код в абстракцию, но это все равно будет хорошим примером того, как трейты могут работать с локальным состоянием объекта в отличии от статических методов. Мы не хотим работать с массивом событий вслепую или перемещать его из объекта. Возможно, мы не хотим добавлять еще одну абстракцию внутрь модели, и нам, конечно же, не хочется копипастить этот шаблонный код везде. И тут опять нам помогут трейты.

Другими практическими примерами могут служить настраиваемое логирование функций, дамп нескольких свойств сразу или общая итерационная/поисковая логика. Мы могли бы решить все эти задачи родительским классом, но поговорим об этом чуть позже.

Итак, трейты являются хорошей заменой в подобных случаях, но это не значит, что статические методы бесполезны. На самом деле, я все же предпочитаю использовать статические методы в случаях, когда не нужно изменять внутреннее состояние объекта, поскольку это всегда безопасней. Статические методы также намного удобнее в тестировании, не требуют mock класса.

Создание утверждений является хорошим примером тех случаев, где я предпочитаю статические методы, несмотря на то, что их обычно можно поместить в трейты. Я нахожу, что Assertion::positiveNumber($int) дает мне вышеупомянутые преимущества и мне легче понять что делает вызываемый класс.

Так что заявляю: Я использую трейты, когда я хочу воздействия, которому требуется доступ к внутреннему состоянию объекта.

Родительские классы

Все что мы перечислили также можно реализовать через наследование. В EventGeneratingEntity возможно такой подход был бы даже лучше, поскольку массив событий действительно будет индивидуальным. Однако, трейты дают возможность множественного наследования вместо одного базового класса. Помимо набора функций, есть ли еще хорошие аргументы за такой подход?

При прочих равных, я бы ориентировался на правило вроде «Является-A против Имеет-A». Конечно, это не точное правило, потому что трейты не являются композицией, но разумный ориентир.

Другими словами, родительские классы нужно использовать для функций, которые присуще какому-то объекту. Родительские классы хорошо передают другим разработчикам смысл кода: «Сотрудник — это человек». Если нам необходимо воздействие, это не означает, что код не должен быть коммуникативным.

Для другой непрофильной функциональности объекта (логирование, события и т.д.) трейты являются подходящим инструментом. Они не определяют характер класса, это вспомогательные функции, или еще лучше — деталь реализации. Все что вы получите от трейта должно находиться на службе у главной цели объекта, трейты не должны стать важной частью функциональности.

Так что, в случае генерации событий, я все-таки предпочту трейт, потому что создание событий — это вспомогательный функционал.

Интерфейсы

Я редко (если вообще) расширяю класс или создаю трейт без сопутствующего создания интерфейса.

Если вы будете следовать этому правилу, то обнаружите, что трейты хорошо дополняют принципы разделения интерфейсов. Легко определить интерфейс для дополнительной функциональности и сделать простую реализацию в трейте по умолчанию.

Это позволяет целевому классу реализовывать свою собственную версию интерфейса или использовать трейт по-умолчанию для неважных случаев. Если ваш выбор — это шаблоны и форсирование бедной абстракции, трейты могут быть мощным союзником.

И еще, если у вас только один реализующий интерфейс класс в коде и вы не планируете это менять, то имплементируйте функционал прямо в нем, не беспокойтесь о трейтах. Таким образом вы сделаете ваш код более читабельным и поддерживаемым.

Когда я не использую трейты

Честно говоря, я не использую трейты довольно часто, возможно раз в несколько месяцев я создаю трейт при постоянной работе над проектами. Вся эта эвристика, которую я обрисовывал (воздействие, требующее доступа к внутреннему состоянию), является крайне нишевой. Если вы используете их слишком часто, возможно вам нужно сделать шаг назад и пересмотреть свой стиль программирования. Есть хороший шанс, что тысячи классов только и ждут, чтобы быть реализованными.

Есть несколько мест, где я не люблю использовать трейты из-за стилевых предпочтений:

Источник

Образовательный портал