When to use a trait?

Posted on by Matthias Noback

When to use a trait? Never.

Well, a trait could be considered to have a few benefits:

Benefits

  1. If you want to reuse some code between multiple classes, using a trait is an alternative for extending the class. In that case the trait may be the better option because it doesn't become part of the type hierarchy, i.e. a class that uses a trait isn't "an instance of that trait".
  2. A trait can save you some manual copy/paste-ing by offering compile-time copy/paste-ing instead.

Downsides

On the other hand, there are several problems with traits. For instance:

  • When a trait adds one or more public methods to a class, you'll often experience the need to define those methods as an interface that the class will automatically implement by using the trait. That's impossible. A trait can't implement an interface. So you have to use the trait and implement the interface explicitly if you want to achieve this.
  • Traits have no "private" methods and properties of their own. I often like to hide some things from the class where the trait is used, but it's impossible to make things "trait-private". Anything defined as private in the trait will still be accessible by the class where the trait is used. Which makes it impossible for a trait to encapsulate anything.

Better alternatives

Anyway, in practice, I always find better alternatives for using a trait. As an example, here are some alternatives:

  • If the trait has service responsibilities, it's better to turn it into an actual service instead, which can and should be injected as a constructor argument into the service that requires this service. This is known as composing behavior, instead of inheriting behavior. This approach can also be used when you want to get rid of parent classes.
  • If the trait adds some behavior to an entity that is the same for another entity, it often makes sense to introduce a value object and use it instead.
  • Another option when you have the same behavior in different parts of the model, is to just copy the code. This will keep the design of each object flexible enough, so each can evolve in its own direction. When we want to change the logic, we won't have to worry about other places that reuse the same logic.

A counter-example

Even though most situation don't really need a trait, and there are better alternatives, there is one situation that I keep using a trait for. This is the code for that trait, and if you ever attended one of my workshops, you'll know it already:

trait EventRecording
{
    /**
     * @var list<object>
     */
    private array $events = [];

    private function recordThat(object $event): void
    {
        $this->events[] = $event;
    }

    /**
     * @return list<object>
     */
    public function releaseEvents(): array
    {
        $events = $this->events;

        $this->events = [];

        return $events;
    }
}

I use this trait in entities, because there I always want to do the same thing: record any number of events, then after saving the modified state of the entity, release those events in order to dispatch them.

One concern might be that releaseEvents() is a public method. But it doesn't have to be on any interface. We don't need an Entity interface, or a RecordsEvents interface, unless we want to create some reusable code that can save an entity and dispatch its recorded events.

Another concern is that this trait suffers from the lack of "trait-private". As an example, instead of using $this->recordThat(...), an entity that uses this trait could also just do $this->events[] = ....

We could fix this issue by extracting the code into an object, (adding even more evidence to the statement that there's always an alternative for using a trait):

final class EventRecording
{
    /**
     * @var list<object>
     */
    private array $events = [];

    public function recordThat(object $event): void
    {
        // ...
    }

    /**
     * @return list<object>
     */
    public function releaseEvents(): array
    {
        // ...
    }
}

We then need to assign an instance of this new class to a private property of the entity:

final class SomeEntity
{
    // Can we do this already? I forgot
    private EventRecording $eventRecording = new EventRecording();

    /**
     * @return list<object>
     */
    public function releaseEvents(): array
    {
        return $this->eventRecording->releaseEvents();
    } 

    // ...
}

Every entity would still need that public method releaseEvents(), and that additional private property, which we copy/paste into each new entity class. We could again introduce a trait for that:

trait ReleaseEvents
{
    private EventRecording $eventRecording = new EventRecording();

    /**
     * @return list<object>
     */
    public function releaseEvents(): array
    {
        return $this->eventRecording->releaseEvents();
    } 
}

At this point I feel like the extracted EventRecording class doesn't do too much anyway, and may be a considered a Lazy class (code smell). We might just as well use the original trait, accept the design concerns, and be done. Fine.

Request for Traits

Based on my experience with traits (having seen examples of them in projects, frameworks, and libraries), I don't know of any good case for using traits, so my rule is to never use them. But, you probably have some good examples of traits that make sense, and only make sense as a trait. So here is my Request for Traits. Please share your examples by posting a comment!

PHP object design traits
Comments
This website uses MailComments: you can send your comments to this post by email. Read more about MailComments, including suggestions for writing your comments (in HTML or Markdown).
Stefano Fago
It becomes a bad practice to use traits to compose a class respecting a specific interface?
Example: https://3v4l.org/gDrME

Logically there's the problem that we are still composing behaviourand not types... but can it be useful as a use-case other than for tests?
Thx for All.
Regards
Stefano
Akihito Koriyama

I generally don't use Trait either, with the only exception that I used Trait only to avoid boiler code for dependency injection.

trait PsrLoggerInject
{
    /** @var LoggerInterface */
    private $logger;

    /** @Inject */
    public function setPsrLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }
}

I know that constructor injection is preferable, but I used this trait for its listability and simplicity. However, with the availability of Constructor property promotion in PHP 8.1, I no longer use these "DI" traits.

Paul Freeman

Here would be my rules of thumb:

  1. If you need an interface, then extend the class.
  2. If you want to add functionality to someone else’s class, you have a choice of extending or using a trait. Personally I would use a Trait as it is simpler.
  3. If you want to add the exact same functionality to many classes, use a trait.

Otherwise just include the functionality in your own original class.

Mailo

We use them essentially to make reusable phpunit assertions, found it is a good solution, maybe there is a better one 🧐

Matthias Noback

Indeed, based on your comment and the replies on Twitter it seems that a very common use case for traits is in TestCase classes, to share assertions and helper functions without changing the class hierarchy. One rule I'd introduce here is that a trait should not rely on facilities provided by the class that uses it, since you could use a trait in classes that are not TestCase and it would no longer work. So it should be self-contained and only rely on properties and methods it owns/declares. Regarding assertions, one way to do that is to switch from self::assertEquals() to PHPUnit\Framework\Assert::assertEquals(), etc.

Doeke Norg
We have these things called components (great name, legacy). They essentially are pages with customizable functionality. But all extend a base component.
Some of these pages need the same behavior (eg. have a link) For this we create a behavior interface, and a trait to use alongside to (partly) satisfy the interface.
This way we can intrduce some common functionality which only makes sense on components, without extending the same class, and avoiding a massive God object on the base component.
We have some rules though:- no functions that already exist on the component, to ensure thought through opt-in, and avoid unwanted side effects - no __construct or other magic methods- every parameter prefixed by the behavior name (link_...)- every method contains the behavior name (getLinkTitle)
This works for us in part because of the legacy code. Would rather not use this.
As for the rest, because traits are copy pasted to the class instead of reused, technically it's still code duplication 😉
Doeke
Matthias Noback

That certainly makes sense. I think traits are mostly useful in either "brownfield" situations, to ease maintenance of otherwise hard-to-maintain code, but also in places where integration with third-party code doesn't allow for other/better alternatives.

Kamil Kokot

Since EventRecording::recordThat is private, how can we use it?

Matthias Noback

Sorry, that should've been "public"; already fixed it!