PHP: Когда использовать трейт?
Когда использовать трейт? Никогда. Для трейта всегда есть лучшая альтернатива. В любом случае на практике я всегда нахожу лучшие альтернативы трейту.
Ну, можно считать, что у трейта есть несколько преимуществ:
Преимущества трейта
- Если вы хотите повторно использовать некий повторяющийся код в нескольких классах, использования трейта является альтернативой для расширения класса. В этом случае трейт (trait) может быть лучшим вариантом, потому что он не становится частью иерархии типов, то есть класс, который использует трейт, не является экземпляром этого трейта .
- Трейт избавит вас от копирования/вставки, предлагая вместо этого копирование/вставку во время компиляции.
Недостатки трейта
С другой стороны есть несколько проблем с трейтами. Например:
- Когда трейт добавляет в класс один или несколько публичных методов, часто возникает необходимость определить эти методы, как интерфейс автоматически реализуемый с помощью трейта. Это невозможно. Трейт не может реализовать интерфейс. Таким образом, вы должны использовать трейт и явно реализовывать интерфейс, если хотите добиться этого.
- Трейты не имеют приватных методов и свойств. Мне нравится скрывать некоторые вещи от класса, в котором используется трейт, но невозможно реализовать их приватным трейтом . Всё, что определено, как приватное в трейте, будет доступно классу, в котором используется трейт. Что делает невозможным для трейта инкапсулировать что-либо.
Лучшие альтернативы
В любом случае на практике я всегда нахожу лучшие альтернативы трейту. В качестве примера, вот несколько вариантов:
- Если у трейта есть служебные обязанности, лучше превратить его в реальную службу (service) внедряемую в качестве аргумента конструктора в службу, которая требует эту службу. Это известно как создание поведения, а не наследование поведения. Также, этот подход можно использовать, когда вы хотите избавиться от родительских классов.
- Если трейт добавляет сущности какое-то поведение, которые является одинаковым для другой сущности, часто имеет смысл ввести объект-значение и использовать его вместо трейта.
- Другой вариант, когда у вас одинаковое поведение в разных частях модели, — просто скопируйте код. Это позволит сохранить дизайн каждого объекта достаточно гибким, и каждый сможет развиваться в своём собственном направлении. Когда мы захотим изменить логику, на не придётся беспокоиться и других местах, которые повторно используют ту же логику.
Контрпример
Несмотря на то, что в большинстве ситуаций трейт на самом деле не нужен, и есть лучшие альтернативы, есть одна ситуация для которой я продолжаю использовать трейт. Вот код этого трейта, если вы когда-нибудь посещали один из моих семинаров, вы уже знаете его:
trait EventRecording
/**
* @var list
*/
private array $events = [];
private function recordThat(object $event): void
$this->events[] = $event;
>
/**
* @return list
*/
public function releaseEvents(): array
$events = $this->events;
$this->events = [];
return $events;
>
>
Я использую этот трейт в сущностях, потому что там я всегда хочу делать одно и то же: записывать некоторое количество событий, затем после сохранения изменённого состояния сущности, отправляет и очищает эти события.
Одна проблема может заключаться в том, что метод releaseEvents() является публичным. Но он необязательно должен быть в каком-либо интерфейсе. Нам не нужен интерфейс Entity или интерфейс RecordsEvents , если только мы не хотим создать повторно используемый код, который может сохранять сущность и отправлять её записанные события.
Другая проблема в том, что трейт страдает от отсутствия приватного трейта . Например, вместо $this->recordThat(. ) , сущность использующая этот трейт, может просто выполнить $this->events[] = . .
Мы могли бы решить эту проблему извлекая код в объект (добавляя ещё больше доказательств к утверждению, что всегда есть альтернатива трейту):
final class EventRecording
/**
* @var list
*/
private array $events = [];
public function recordThat(object $event): void
// .
>
/**
* @return list
*/
public function releaseEvents(): array
// .
>
>
Затем нам нужно присвоить экземпляр этого нового класса приватному свойству сущности:
final class SomeEntity
// Можем ли мы уже сделать это? Я забыл
private EventRecording $eventRecording = new EventRecording();
/**
* @return list
*/
public function releaseEvents(): array
return $this->eventRecording->releaseEvents();
>
// .
>
Каждой сущности по-прежнему нужен этот публичный метод releaseEvents() и это действительно дополнительно приватное свойство, которое мы копируем/вставляем в каждый новый класс сущности. Мы могли бы снова ввести трейт для этого:
trait ReleaseEvents
private EventRecording $eventRecording = new EventRecording();
/**
* @return list
*/
public function releaseEvents(): array
return $this->eventRecording->releaseEvents();
>
>
Я чувствую, что извлечённый класс EventRecording в любом случае не слишком много делает и может считаться Ленивым классом (дурно пахнущий код). С таким же успехом мы могли бы использовать оригинальный трейт, принять проблемы дизайна и покончить с этим. Отлично.
Запрос Трейта
Основываясь на своём опыте работы с трейтами (я видел их примеры в проектах, фреймворках и библиотеках), я не знаю ни одного подходящего случая для использования трейтов, поэтому моё правило — никогда их не использовать. Но у вас, вероятно есть несколько хороших примеров трейтов, которые имеют смысл, и имеют смысл только как трейт. Итак, вот мой запрос трейта. Пожалуйста, поделитесь своими примерами в комментарии!
PHP: Разница между Трейтом, Интерфейсом и Абстрактным классом
Эти три структуры могут сбить с толку новичков в PHP и опытных разработчиков, поэтому давайте рассмотрим, что делает каждая из них и когда их лучше использовать
Работа с трейтами в ООП на PHP
Как вы уже знаете, в PHP нельзя наследовать от нескольких классов сразу, только от одного. Ранее мы уже проходили решение этой проблемы: вместо наследования использовать объекты одних классов внутри других.
В PHP есть и другой способ. Он заключается в использовании . Трейт представляет собой набор свойств и методов, которые можно включить в другой класс. При этом свойства и методы трейта будут восприниматься классом будто свои.
Синтаксис трейта такой же как и у класса, за исключением того, что имя трейта нужно объявлять с помощью ключевого слова trait .
Экземпляр трейта нельзя создать — трейты предназначены только для подключения к другим классам. Само подключение осуществляется с помощью команды use , после которой через пробел указывается имя подключаемого трейта. Данная команда пишется в начале класса.
Давайте посмотрим применение трейтов на практическом примере. Пусть у нас дан вот такой трейт Helper , содержащий приватные свойства name и age , а также их геттеры:
name; > public function getAge() < return $this->age; > > ?>?php>
Пусть у нас также есть вот такой класс User , в конструкторе которого задаются свойства name и age :
name = $name; $this->age = $age; > > ?>?php>
Давайте теперь добавим геттеры для свойств нашего класса User . Только не будем их записывать в самом классе, а просто подключим трейт Helper , в котором эти методы уже реализованы:
name = $name; $this->age = $age; > > ?>?php>
После подключения трейта в нашем классе появятся методы и свойства этого трейта. При этом обращаться мы к ним будем будто к методам и свойствам самого класса:
getName(); // выведет ‘john’ echo $user->getAge(); // выведет 30 ?>?php>
Для того, чтобы продемонстрировать преимущества трейтов, давайте сделаем еще один класс City (город). У города также будет имя и возраст, однако, логично, что город и юзер не могут наследовать от одного родителя, так представляют собой немного разные сущности, пусть и имеющие похожие методы.
Поэтому воспользуемся созданным нами трейтом Helper и в классе City :
name = $name; $this->age = $age; > > ?>?php>
Проверим работу нашего класса:
getName(); // выведет ‘Minsk’ echo $city->getAge(); // выведет 1000 ?>?php>
Реализуйте класс Country со свойствами name , age , population и геттерами для них. Пусть наш класс для сокращения своего кода использует уже созданный нами трейт Helper .
Несколько трейтов
В классе можно использовать не один, а несколько трейтов. В этом и проявляется их преимущество перед наследованием. Нужные для использования в классе трейты можно указать через запятую после ключевого слова use .
Сделайте 3 трейта с названиями Trait1 , Trait2 и Trait3 . Пусть в первом трейте будет метод method1 , возвращающий 1 , во втором трейте — метод method2 , возвращающий 2 , а в третьем трейте — метод method3 , возвращающий 3 . Пусть все эти методы будут приватными.
Сделайте класс Test , использующий все три созданных нами трейта. Сделайте в этом классе публичный метод getSum , возвращающий сумму результатов методов подключенных трейтов.
Что такое traits php
Traits представляют группу методов, которые могут быть добавлены в классы. Traits позволяют определять блоки функционала и многократно повторно использовать в классах без необходимости усложнять код классов, которые используют эти методы.
Traits определяются с помощью ключевого словва trait , после которого идет название трейта:
trait Printer
Traits могут содержать только статические и нестатические методы:
trait Printer < public function printSimpleText($text) < echo "$text
"; > public function printHeaderText($text) < echo "$text"; > >
Для применения классов трейта применяется оператор use , после которого указывается добавляемый трейт:
trait Printer < public function printSimpleText($text) < echo "$text
"; > public function printHeaderText($text) < echo "$text"; > > class Message < use Printer; >$myMessage = new Message(); $myMessage->printSimpleText("Hello World!"); $myMessage->printHeaderText("Hello PHP 8");
После добавления трейта с помощью оператора use класс может использовать его методы, как-будто они определены в самом этом классе.
Следует учитывать, что при наследовании методы трейта переопределяют унаследованные методы с тем же именем:
class Data < function print() < echo "Print from Data"; >> trait Printer < function print() < echo "Print from Printer"; >> class Message extends Data < use Printer; >$myMessage = new Message(); $myMessage->print(); // Print from Printer
Трейты в PHP
Трейты (англ. traits) — это механизм обеспечения повторного использования кода в языках с поддержкой единого наследования. В php трейты внедрены с версии 5.4.0.
Они позволяют разработчику повторно использовать наборы методов и свойств в нескольких независимых классах.
Трейт похож на класс, но создать экземпляр трейта невозможно. Он предназначен для группирования функционала, который потом используется в разных классах.
Пример использования.
trait HelloWorld < public function sayHello() < return 'Привет '; >> class NameMen < public $name = 'Вася'; >class GetInfo extends NameMen< use HelloWorld; public function getPiple()< echo $this->sayHello() . $this->name; > > $obj = new GetInfo(); $obj->getPiple(); //Привет Вася
Тут создается трейт HelloWorld с публичным методом sayHello(). Все точно так же как в обычном классе. Далее создаем два класса. Класс GetInfo наследует класс NameMen для того, чтобы получить доступ к его методам и свойствам с помощью ключевого слова extends. Точно так же класс получает такой доступ к свойствам и методам трейтов с помощью директивы use , после которой указывается название трейта.
Нужные для использования в классе трейты можно указать через запятую:
use trait1, trait2, trait3;
Приоритет методов при работе с трейтами.
— члены из текущего класса переопределяют одноименные методы в трейте,
— члены из трейта переопределяют унаследованные классом методы. То есть трейт имеет преимущество перед классом который наследуем.
Конфликты трейтов.
Ошибки могут быть когда подключается несколько трейтов, содержащие одни и те же методы. Или когда класс наследует у другого класса с подключением трейта, который уже был подключен в родительском классе.
Для разрешения конфликтов необходимо использовать оператор insteadof при подключении трейтов для того, чтобы точно выбрать один из конфликтных методов.
trait Hello1 < public function sayHello() < return 'Hello'; >> trait Hello2 < public function sayHello() < return 'Привет'; >> class GetInfo < use Hello1,Hello2< Hello2::sayHello insteadof Hello1; >public function getPiple()< echo $this->sayHello(); > > $obj = new GetInfo(); $obj->getPiple(); //Привет
Внутри тела «use» мы использовали ключевое слово insteadof, слева от которого указывается трейт, метод которого будем использовать и имя самого метода, которые разделяются двойным двоеточием. В правой части указывается имя трейта, метод которого должен быть заменён.
Если же второй одноименный метод (из другого трейта) нам тоже нужен, то можно применить псевдоним имени используя ключевое слово as :
use Hello1,Hello2 < Hello2::sayHello insteadof Hello1; Hello1::sayHello as hel; >public function getPiple()< echo $this->sayHello(); echo $this->hel(); >
Статические методы и свойства
Используются так же как и в классах:
trait HelloWorld < static $age = 'Вася'; static function sayHello() < echo 'Привет '; >> class GetInfo< use HelloWorld; public function getPiple()< echo $this->sayHello(); > > GetInfo::sayHello(); //Привет echo GetInfo::$age; //Вася
Доступ к свойствам базового класса.
В трейтах для доступа к свойствам базового класса можно использовать псевдопеременную $this.
Изменения прав доступа к методам трейта.
Внутри трейта мы можем использовать любой модификатор доступа (public, private, protected) для методов. Но, кроме этого, есть возможность в классе менять этот модификатор на другой. Для этого в теле use после слова as можно указать новый модификатор.
use someTrait
Константы связанные с трейтами.
trait HelloWorld < static $name_trait = __TRAIT__; >class GetInfo < use HelloWorld; >echo GetInfo::$name_trait; //HelloWorld
Итоги:
- Трейты используются, когда нужно набор методов вставить в разные классы, они чем-то похожи на команду include.
- Трейты объявляются используя ключевое слово trait, в классе использование трейта прописывается ключевым словом use.
- В одном классе можно использовать несколько трейтов.
- Невозможно создать самостоятельный экземпляр трейта.
- Трейты можно использовать совместно с интерфейсами.
- Конфликты имён в трейтах устраняется при помощи ключевого слова insteadof, которое замещает метод.
- Замещённому методу можно присвоить псевдоним при помощи ключевого слова as.
- При помощи ключевого слова as также можно изменить область видимости метода трейта в классе.
- В трейте можно использовать псевдопеременную $this для доступа с свойствам базового класса.
- В трейтах можно задавать абстрактные классы, которые должны быть реализованы в классах, использующих эти трейты.
- К методам трейта можно обратиться напрямую, используя оператор двойного двоеточия.
Автор: Сергей Дата публикации: 11.02.2017
- Пример простого REST API на PHP.
- Использование YouTube Data API для своего WEB-сервиса. Выборка нужных видеозаписей, получение информации.
- Определение страны и города посетителя по его IP. Расширение GeoIP.
- Установка, настройка и базовое использование фреймворка для тестирования «Codeception».
- Основы PHPUnit — 1 часть.
