PHP Учебник

PHP Старт Введение в PHP Установка PHP Синтаксис PHP Комментарии в PHP Переменные PHP PHP Echo / Print Типы данных PHP Строки PHP Числа PHP Математика в PHP Константы PHP Операторы PHP PHP If...Else...Elseif PHP Switch Циклы в PHP Функции PHP Массивы PHP PHP Суперглобальные PHP RegEx

PHP Формы

Обработка форм PHP Валидация форм PHP Обязательные поля Валидация URL/E-mail Полная форма PHP

PHP Продвинутый

PHP Дата и время PHP Include/Require PHP Работа с файлами Открытие/Чтение файлов Создание/Запись файлов PHP Загрузка файлов Файлы cookie PHP Сессии PHP Фильтры PHP Расширенные фильтры PHP Функция Callback PHP JSON PHP Исключения

PHP OOP

Что такое ООП в PHP Классы/Объекты PHP Цепочки методов PHP Конструктор PHP Деструктор PHP Модификаторы доступа Наследование в PHP Константы класса PHP Подсказка типов PHP Подсказка интерфейсов Абстрактные классы PHP PHP Интерфейсы PHP Полиформизм PHP Трейты Статические методы PHP Статические свойства PHP Пространства имен PHP Итерируемые объекты

База данных MySQL

База данных MySQL Подключение к MySQL Создание БД MySQL Создание таблицы MySQL Вставка данных MySQL Получить ID MySQL Подготовленные операторы PHP MySQL Получение данных MySQL Предложение WHERE Предложение ORDER BY Обновление данных MySQL Удаление данных БД MySQL Limit Data

PHP XML

Парсеры PHP XML Парсер PHP SimpleXML Получить PHP SimpleXML PHP XML Expat PHP XML DOM

PHP - AJAX

AJAX Введение AJAX PHP AJAX База Данных AJAX XML AJAX Живой поиск AJAX Опрос

PHP Примеры

PHP Примеры Практика ООП PHP PHP квиз-тест Упражнения Базовый PHP Упражнения Алгоритмы Упражнения Массивы Упражнения Цикл for Упражнения Функции Регулярные выражения Упражнения Дата PHP Упражнения Строки PHP Математика PHP Упражнения Формы PHP Упражнения Классы PHP Упражнения JSON PHP PHP Задачник


Трейты в PHP




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


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

Синтаксис трейта такой же как и у класса, за исключением того, что имя трейта нужно объявлять с помощью ключевого слова trait:

Синтаксис

<?php
trait TraitName {
  // некоторый код...
}
?>

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

Само подключение осуществляется с помощью ключевого слова use, после которого через пробел указывается имя подключаемого трейта. Данная команда пишется в начале класса:

Синтаксис

<?php
class MyClass {
  use TraitName;
}
?>

Давайте посмотрим на пример в котором объявим один трейт и подключим к классу:

<?php
trait Reader {
  public function add($var1,$var2){
     return $var1+$var2;
  }
}

class File {
  use Reader; // подключаем трейт
  public function calculate($var1,$var2) {
     echo "Результат сложения: ".$this->add($var1,$var2) ."\n";     
  }
}
$o = new File();
$o->calculate(5,3);
?>

Результат выполнения кода:

Результат сложения: 8

В примере выше мы объявляем один трейт Reader. Затем мы создаем класс File. Класс использует трейт, и все методы, объявленные в трейте, будут доступны в классе. При этом обращаться мы к ним будем будто к методам самого класса.

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

По сути, трейт — это просто способ скопировать и вставить код во время выполнения.

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

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

Первый класс File использует трейт Reader, а второй класс File2 использует трейты Reader и writer (несколько трейтов разделяются запятыми):

<?php
trait Reader{
  public function add($var1,$var2){
     return $var1+$var2;
  }
}
trait writer {
  public function multiplication($var1,$var2){
     return $var1*$var2;
  }
}
class File {
  use Reader;
  public function calculate($var1,$var2){
     echo "Результат сложения: ".$this->add($var1,$var2) ."\n";
  }
}
class File2 {
  use Reader;
  use writer; // альтернатива записи: use Reader, writer;
  public function calculate($var1,$var2){
     echo "Результат сложения: ".$this->add($var1,$var2) ."\n";
     echo "Результат умножения: ".$this->multiplication($var1,$var2);
  }
}
$o = new File();
$o->calculate(5,3);

$o2 = new File2();
$o2->calculate(6,4);
?>

Результат выполнения кода:

Результат сложения: 8
Результат сложения: 10
Результат умножения: 24

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

Интерфейс — это контракт, в котором говорится, что "этот объект может делать это", тогда как трейт дает объекту возможность делать это.

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

Интерфейсы — это спецификации, которые можно проверить используя оператор instanceof (является ли текущий объект экземпляром указанного класса).

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

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

Например:

<?php
interface Person {
    public function greet();
    public function eat($food);
}

trait EatingTrait {
    public function eat($food) {
        $this->putInMouth($food);
    }

    private function putInMouth($food) {
        // Переваривайте вкусную еду
    }
}

class NicePerson implements Person {
    use EatingTrait;

    public function greet() {
        echo 'Добрый день, сэр!';
    }
}

class MeanPerson implements Person {
    use EatingTrait;

    public function greet() {
        echo 'Твоя дочь на ужине была хомяком!';
    }
}

$niceperson = new NicePerson;
if($niceperson instanceOf Person) {  
    $niceperson->greet();  
}  
echo "<br>";
$meanperson = new MeanPerson;
if($meanperson instanceOf Person) {  
    $meanperson->greet();  
}  
?>

Результат выполнения кода:

Добрый день, сэр!
Твоя дочь на ужине была хомяком!

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

Как описано в Руководстве: Унаследованный метод от базового класса переопределяется методом, вставленным с помощью трейта. Порядок приоритета таков — методы текущего класса переопределяют методы трейта, которые в свою очередь переопределяют унаследованные методы.

Итак, рассмотрим следующий сценарий:

<?php
class BaseClass {
    function SomeMethod() { echo "BaseClass"; }
}

interface IBase {
    function SomeMethod();
}

trait myTrait {
    function SomeMethod() { echo "myTrait"; }
}

class MyClass extends BaseClass implements IBase {
    use myTrait;

    function SomeMethod() { echo "MyClass"; }
}

$myclass = new MyClass; 
$myclass->SomeMethod(); 
?>

Результат выполнения кода:

MyClass

При создании экземпляра MyClass, описанного выше, происходит следующее:

  1. Интерфейс IBaseвключают абстрактный метод без параметров под названием SomeMethod(), который должен быть реализован в наследуемом классе.
  2. Базовый класс BaseClass предоставляет реализацию этого метода SomeMethod().
  3. У трейта myTraitтакже есть вызываемая функция без параметров SomeMethod(), которая имеет приоритет над BaseClass-версиями.
  4. Класс MyClass предоставляет свою собственную версию метода SomeMethod(), которая имеет приоритет над трейт-версиями.

Заключение

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