When should i use trait in php?

Photo by Larry Nalzaro on Unsplash

Since PHP version 5.4.0 the language has had the keyword — trait. The main purpose of PHP Traits is to solve the single inheritance limitations of the language, because in PHP you can't do extends Class1, Class2. Traits contain a declaration of the methods or the properties that you can easily apply to the classes using the keyword use.

trait SomeTrait
{
public function traitMethod[]
{
// ...
}
}
class SomeClass
{
use SomeTrait;

public function method[]
{
// You have access to the Trait method $this->traitMethod[];
}
}

PHP has the function class_uses[] that allows you to get the array of Traits that are used:

class_uses[SomeClass::class]

Use cases

The shared code can be placed into a Trait to prevent duplicates of the code and use a Trait easily in your classes. Laravel uses this technique everywhere :]

Using Traits in Laravel makes it easy to get your model to work with a full-text search engine simply by adding the line use Searchable into the model.

Laravel Nova uses the Actionable trait to log the Action activity of your Eloquent model.

But be careful a Trait can’t be “de-use” [disabled]. If you need to have a class that uses a Trait and sometime you’ll decide to use this class somewhere without Trait just decompose it into parts using inheritance and an abstract class. For example, you have the Searchable trait and you want to have the Product class without this Trait:

abstract AbstractProduct
{
// The product properties and methods
}
class Product extends AbstractProduct
{
use Searchable;
}
class ProductWithoutSeachable extends AbstractProduct
{
// Nothing here
}

The properties

As I said, the trait can contain more than methods, it also can contain a definition of properties:

trait SomeTrait
{
private $value;
}

The private visibility of the property will be applied to the class which will use this Trait.

Resolve naming conflicts

If you have similar names of the methods in the Trait and class and you want to call the method from the Trait in the class, you can resolve it by creating alias using the keyword as.

For example, the Trait and the class have the method doSomething and you want to call the doSomething which in the Trait from the class method doSomething:

trait SomeTrait
{
public function doSomething[]
{
echo 'doSomething from trait' . PHP_EOL;
}
}
class SomeClass
{
use SomeTrait {
doSomething as traitDoSomething;
}

public function doSomething[]
{
$this->traitDoSomething[];
echo 'doSomething from class' . PHP_EOL;
}
}

$obj = new SomeClass[];
$obj->doSomething[];

The output of the above code:

doSomething from trait
doSomething from class

I hope after reading the article you know more than you knew before about the power of Traits in PHP.

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
     */
    private array $events = [];

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

    /**
     * @return list
     */
    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
     */
    private array $events = [];

    public function recordThat[object $event]: void
    {
        // ...
    }

    /**
     * @return list
     */
    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
     */
    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
     */
    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!

When should I use traits?

Traits are used to declare methods that can be used in multiple classes. Traits can have methods and abstract methods that can be used in multiple classes, and the methods can have any access modifier [public, private, or protected].

Is PHP trait good?

PHP Traits are Bad On the surface, there is strong support for PHP traits because using them can help reduce code duplication throughout your application. In addition, they can help improve maintainability and the cleanliness of your code.

Why do we need traits?

Traits can define both static members and static methods. It helps developers to reuse methods freely in several independent classes in different class hierarchies. Traits reduces the complexity, and avoids problems associated with multiple inheritance and Mixins.

When did PHP add traits?

Traits was introduced in PHP 5.4 and it's so cool and easy to use. Traits is much more similar to a class but it is only for grouping methods in a fine-grained and consistent way. You can't instantiate a trait like you would do a class, in fact, It is not possible to instantiate a Trait on its own.

Chủ Đề