Что такое трейты в PHP и как их использовать

22 Сентября 2022 (ред)

Трейты в PHP

Трейты в PHP существуют с версии 5.4, то есть относительно давно, они добавляют возможность переиспользования кода без наследования, когда один и тот же код можно использовать в совершенно не связанных между собой классах. Факт использования трейтов во фреймворке Symfony, говорит о том, что они могут без проблем вписаться в современные архитектурные решения кода и ничуть не устарели. В целом, они довольно успешно заменяли классическую "копи-пасту", пока, вместе с развитием языка PHP, к трейтам не стало всё больше и больше претензий в ракурсе ООП. О недостатках упоминали и раньше, сразу же после выхода новинки.

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

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

Подробное определение (взято из Википедии):

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

Да, в PHP наследовать можно только от одного класса. Если вы выстроили две независимые ветви наследования, а участок кода в них всё равно повторяется, то здесь можно использовать трейт. Также многие используют трейты для "нарезки" класса на части (рекомендую в этом случае делать только приватные методы трейта, а публичные - только в классе). Это самое невинное использование трейта и врятли кто-то будет против. Вы разделили класс на логические составляющие и они лежат где-то рядом с классом в отдельной папке. Но всё же, если речь про переиспользование кода, то правильнее использовать трейты по прямому назначению, но в рамках одного модуля. В общем проекте это будет дублирование кода (трейта), если понадобится в другом модуле. Но код меняется и эти части вполне могут стать непохожими через некоторое время.

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

Трейты есть не только в PHP, например в Rust, где нет классического наследования "родитель-потомок", вместо этого используют трейты (и нечто ещё, называемое "супертрейт"). Трейты есть и в других языках, Scalа, например, в других просто названы по-другому, в C# называются "интерфейсы с реализацией методов по умолчанию".

Интересно, что в PHP трейты могут иметь свойства, при этом в классе не должно быть свойства с таким же именем. Это добавляет ответственности к именованию не только методов, но и свойств, они могут непредсказуемо пересекаться с названиями методов в других трейтах в классе или из самого класса. Как минимум они (названия) не должны быть односложными. В крайне запутанных случаях можно использовать insteadof и as для разрешения таких конфликтов. Минусом трейта может быть невозможность инкапсулировать часть его методов и свойств, даже если они приватные.

Чтобы уменьшить "непредсказуемое" влияние трейтов на проект, можно назначать каждому интерфейс (это вполне отвечает принципу разделения интерфейсов), пользоваться правилом "один трейт - один метод" и делать трейты самодостаточными, то есть, все что трейт использует должно быть в нём же и объявлено.

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

<?php

class A {

    use BTrait;

    public function getValue() {
        return $this->value;
    }
}

trait BTrait {

    private $value = 48;

    public function getTraitValue() {
        return $this->value;
    }

}

$obj = new A();

print $obj->getValue(); // 48

print $obj->getTraitValue(); // 48

В первом абзаце статьи упоминается, что к трейтам есть претензии с точки зрения ООП. Ну конечно, иначе бы ООП называлось Объектно-Трейтовое-Программирование. Естественно, общие практики программирования складывались без конкретных отхождений каждого из языков, таких как трейты в PHP, поэтому общеизвестные паттерны используют что угодно, наследование, композицию, но только не трейты. Так как во всеохватывающих практиках делается попытка объяснить всё, то это "всё" на выходе без ньюансов и дополнительных возможностей языков программирования. В этом нет проблемы, но объясняет расхождение во взглядах у вполне продвинутых программистов, в ином случае, если "не вполне", то написать неподдерживаемый код можно используя что угодно, сам инструмент в этом не виноват.

1 Ответ

  1. fomiash fomiash 17 Октября 2022 (ред.)

    В версии PHP 8.2 ожидается, что в трейтах можно будет использовать константы, но доступ к ним извне (если константа публичная) будет только через классы, на которых распространяется действие трейта.



Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.