Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Трейты PHP: Погружаясь в классы

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

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

<?php

// Magic.php

trait Magic
{
    // Доступно только внутри трейта
    private $properties;

    public function __get($key)
    {
        return $this->properties[$key] ?? null;
    }

    public function __set($key, $value)
    {
        $this->properties[$key] = $value;
    }
}

Трейт по большей части выглядит как (абстрактный) класс и устроен как класс. Он подчиняется тем же правилам именования и расположения в иерархии пространств имён (а следовательно и файловой структуре) что и классы. Отличия начинаются в момент использования:

<?php

// Config.php

class Config
{
    // Включение трейта в класс
    use Magic;
}

$config = new Config();
$config->key = 'value';
echo $config->key;

Трейт включается в другой класс с помощью инструкции use. С этого момента в классе становится доступна вся функциональность, определённая в трейте. По поведению трейты похожи на наследование, например, приватные части трейта доступны только внутри методов самого трейта. Но при этом трейт не встраивается в цепочку наследования, это легко проверить:

<?php

$config instanceof Magic; // false

Из этого есть пара важных следствий:

  • Внутри класса к методам трейта нельзя обратиться через parent, только через $this. При условии, что эти методы не приватные.
  • Трейт не может реализовывать интерфейс. Это могут делать только классы.

Зачем?

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

<?php

class MySuperClass
{
    use FirstTrait;
    // При включении возможны конфликты имён. Подробнее про их разрешение:
    // https://www.php.net/manual/ru/language.oop5.traits.php#language.oop5.traits.conflict
    use SecondTrait;
}

Эта структура располагает к выделению общих признаков из совершенно разнообразных классов. Пример с Magic как раз хорошо демонстрирует такой подход. Многие классы одинаково реализуют магические методы и нет смысла дублировать их код. И точно не стоит использовать наследование, так как оно свяжет совершенно несвязанные классы в общую (и жёсткую) иерархию.

Трейты позволяют реализовать многие интерфейсы PHP универсальным образом, например, ArrayAccess или Iterator

Пример: Итератор

Рассмотрим готовый пример трейта-итератора. Он реализует общую логику обхода коллекций внутри объектов. С его помощью можно сделать объекты, которые можно использовать в forEach:

<?php

// Реализует итератор
$course = new Course();
foreach ($course as $lesson) {
    echo $lesson . "\n";
}

Трейт:

<?php

trait IteratorTrait
{
    protected $offset = 0;

    public function current()
    {
        return $this->getCollection()[$this->offset] ?? null;
    }

    public function next()
    {
        $this->offset++;
    }

    public function key()
    {
        return $this->offset;
    }

    public function valid()
    {
        return \array_key_exists($this->offset, $this->getCollection());
    }

    public function rewind()
    {
        $this->offset = 0;
    }

    abstract public function getCollection();
}

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

Это очень важная концепция. Трейту нужны данные от класса, в который его включают. И трейт строит связь с этими классами через интерфейсный метод, а не через обращение к свойству с конкретным именем. А вот класс от трейта ничего не требует. Благодаря тому, что связь строится в одну сторону (трейт зависит от метода класса, но класс не зависит от методов и свойств трейта), код остаётся модульным. Если бы и трейт требовал что-то от класса и класс от трейта, то почти наверняка в коде проблемы с архитектурой.

<?php

// Обязательно расширять интерфейс Iterator, только тогда PHP поймёт, что это итератор
class Course implements Iterator
{
    // Для простоты свойство наполнено строками
    private $lessons = ['one', 'two', 'three'];
    use IteratorTrait;

    protected function getCollection()
    {
        return $this->lessons;
    }
}

// Использование
$course = new Course();
foreach ($course as $lesson) {
    echo $lesson . "\n";
}
// 'one'
// 'two'
// 'three'

Дополнительные материалы

  1. Трейты

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
900
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 130 курсов, 2000+ часов теории
  • 900 практических заданий в браузере
  • 360 000 студентов
Даю согласие на обработку персональных данных, соглашаюсь с «Политикой конфиденциальности» и «Условиями оказания услуг»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

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

Иконка программы PHP-разработчик
Профессия
Разработка веб-приложений на Laravel
7 июля 10 месяцев

Используйте Хекслет по максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Даю согласие на обработку персональных данных, соглашаюсь с «Политикой конфиденциальности» и «Условиями оказания услуг»