Используем трейты с пользой

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

    Из-за особенности реализации proxy в Doctrine, для любой модели мы обязаны писать геттеры и сеттеры. Но зачем повторять каждый раз одно и то же? Абсолютно каждая сущность имеет id, каждая вторая name и частенько description. Так почему бы это не вынести в трейты?

    namespace Column;
    
    trait Id
    {
        /**
         * @var integer
         * @ORM\Id
         * @ORM\Column(type="integer")
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        protected $id;
    
        /**
         * @return integer
         */
        public function getId()
        {
            return $this->id;
        }
    }
    


    Теперь описывая очередную модель нам достаточно вписать одну строку, вместо 15.
    class Book
    {
    	use \Column\Id;
    }
    

    Ну не здорово ли?

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

    Все довольно просто, как использовать — описано в README.

    Однако эти библиотеки добавляют целые куски логики, а мне хотелось бы просто сократить код моделей.
    Поэтому я вынес большинство используемых трейтов в своем проекте в отдельную либу doctrine-columns.
    Библиотека представляет собой пока-что небольшой набор часто-используемых свойств в классах моделей.
    Допишем Book.

    use Nkt\Column;
    
    class Book
    {
    	use Column\Id;
        use Column\Name;
        use Column\Price;
        use Column\Description;
        use Column\CreatedDate;
        
        public function __construct($name, $description)
        {
        	$this->setName($name);
        	$this->setDescription($description);
        	$this->setCreatedDate(new \DateTime());
        }
    }
    

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

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

    Однако не все так радужно — существует небольшая проблема. Если схема данных у вас берется из аннотаций — ее невозможно переопределить без Strict Standarts варнинга.

    Так или иначе — на мой взгляд это очень интересный подход, в первую очередь облегчающий восприятие кода. Присылайте PRы с вашими часто используемыми свойствами в сущностях, я с удовольствием их добавлю.
    Поделиться публикацией
    Комментарии 14
      +3
      ну… сомнительно на самом деле… Я трейты применяю в контроллерах, выношу в них штуки, которые просто сокращают объемы кода. Например сериализация jmsSerializer-ом, валидация и проверка на ошибки, и т.д. Просто уменьшаю количество дублирования кода без необходимости выносить всю эту радость в базовый класс. А использовать трейты для того, что бы не писать геттеры/сеттеры и пару тройку аннотаций, это все же как по мне не очень. Нельзя зайти сходу в класс и увидеть что есть что и как это все в базе хранится (если с точки зрения разработчика, впервые видящего проект).

      Это нужно в трейты лезть что бы аннотации посмотреть… А если там нужно не только аннотации для Doctrine, но еще и ассерты валидатора расставить (description, price, name), настроить сериализацию по группам и т.д. И уже придется от трейтов либо отказываться, либо дописывать. Причем если у нас две сущьности используют одно и то же поле, например description, и у нас по логике они должны учавствовать в сериализации объекта, но для разных групп, уже плохо выходит.

      Это мое субъективное мнение, вы можете быть с ним не согласны.
        +1
        В принципе критика обоснованна. В php невозможно переопределить свойство из трейта и соответственно phpdoc для него.
        Но этот пост и либа — скорее призыв к тому, чтобы в проектах таки выделять некоторые куски кода в трейты, потому что это реально удобно, особенно для связных сущностей.
          0
          Я согласен с тем что трейты можно и нужно использовать для устранения дублирования кода, но немного не согласен с тем, где. Скажем в контроллерах полно мест, где нужны всякие упрощалки, аля функции render или redirect. Скажем, у меня все это вынесено в трейты, которые подключаются там, где это нужно. Более того, можно сеттеры сервисов добавить, нужные только для этого трейта и т.д. А вот выносить в трейт геттеры/сеттеры полей сущностей я считаю плохой затеей. Мало ли, что потом придется делать с ними. Хотя опять же, может быть кейсы когда это оправдано и есть.

          То есть если код хоть сколько нибудь относится к нашим доменным моделям, или же бизнес логике, то тут лучше трейты не использовать. А если это какой-то сервисный код, который приходится раз от раза переписывать, который делает что-то вроде «достать сервис, подготовить данные, задать какие-то общие настройки и т.д и выполнить метод», то тут определенно да, стоит вынести в трейт.
            0
            Ну вот у меня Column\Name постоянно дублировалось абсолютно одно и то же. Ради избавления от таких дублей — я готов валидацию в yml запихнуть, если честно.
            Как раз логика и теряется во всех этих геттерах/эддерах/ремуверах/сеттерах. Например пользователь хочет указать ссылки на соц-сети. Это либо в массиве хранить, что не правильно, либо в разных полях. Можно запросто их вынести в какой ни будь Socialable и забыть. Понятно, что не везде так, к сожалению, можно
              0
              Скажите, кроме Вас с этим кодом еще кто-то работает?
                0
                да, а что?
        +1
        // Так почему бы это не вынести в трейты?
        А производительность вы замеряли для сравнения?
          +1
          А что там будет с производительностью, позвольте спросить? Мне кажется что при наличии opcache разница будет в пределах погрешности.
            +1
            yadi.sk/d/OkF0-1jeLB4TG
            Вот картинка дифа без и с трейтом.
            Ясно что на одном классе идет оверхед, из-за биндинга, но если классов, использующих трейт больше — трейты уменьшают кол-во опкода пропорционально величине методов.
            +4
            Трейты существуют для реалазиции интерфейсов которые имплементирует класс, тогда и все становится на свои места
            github.com/zendframework/zf2/blob/master/library/Zend/EventManager/EventManagerAwareInterface.php
            github.com/zendframework/zf2/blob/master/library/Zend/EventManager/EventManagerAwareTrait.php

            тогда пример из статьи будет выглядеть
            class Book implements Column\IdInterface, Column\NameInterface, Column\PriceInterface, Column\DescriptionInterface, Column\CreatedDateInterface
            {
                use Column\Id;
                use Column\Name;
                use Column\Price;
                use Column\Description;
                use Column\CreatedDate;
            }
            

              +1
              А на мой взгляд это не так. Трейт — это возможность вынести повторяющийся функционал из класса, то что как-то изменяет класс. Например самый популярный пример трейта — синглтон
                +1
                Тоесть статическая реализация метода getIntance интерфейса SingletonInterface. Там где вы думаете что неплохо бы использовать trait и если это не предполагает интерфейса, в 99% случаев предпочтительнее использовать делегирование.
                class RssReaderService
                {
                      public function setHttpClient(HttpClient $client) {}
                      public function getHttpClient() {}
                }
                
                  +1
                  Вы статью прочли? Я использую трейты для скаляров, в основном. Как к этому вообще относятся сервисы?
                    0
                    Вы чуть выше про трейты для реализации singelton предложили. К статье отношения это не имеет. Трейты для сервисов использовать вообще не стоит.

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

            Самое читаемое