Traits in PHP - Any Real World Examples/Best Practices

Traits in PHP – any real world examples/best practices?

My personal opinion is that there is actually very little application for traits when writing clean code.

Instead of using traits to hack code into a class it is better to pass in the dependencies via the constructor or via setters:

class ClassName {
protected $logger;

public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
// or
public function setLogger(LoggerInterface $logger) {
$this->logger = $logger;
}
}

The main reason why I find that better than using traits is that your code is much more flexible by removing the hard coupling to a trait. For example you could simply pass a different logger class now. This makes your code reusable and testable.

Traits in php- Any real world example

Let there be 2 classes : Mailer and Writer.

Mailer sends some text by mail, where Writer writes text in a file.

Now imagine you want to format the input text used by both classes.

Both classes will you use the same logic.

  • You could create an interface, but you will need to duplicate the logic in both classes.
  • You could create a parent class and extend it, but PHP doesn't allow inheritance of multiple classes. This will become a problem if your Mailer and Writer classes already extends some classes.

So you use a trait

Example :

trait Formatter
{
public function format($data)
{
// Do some stuff
return $data;
}
}

class Mailer
{
use Formatter;

public function send($data)
{
$data = $this->format($data);
// Send your mail
}
}

class Writer
{
use Formatter;

public function write($data)
{
$data = $this->format($data);
// Write in file
}
}

In PHP, traits are like 'mini-classes'.

Traits in php- Any real world example

Let there be 2 classes : Mailer and Writer.

Mailer sends some text by mail, where Writer writes text in a file.

Now imagine you want to format the input text used by both classes.

Both classes will you use the same logic.

  • You could create an interface, but you will need to duplicate the logic in both classes.
  • You could create a parent class and extend it, but PHP doesn't allow inheritance of multiple classes. This will become a problem if your Mailer and Writer classes already extends some classes.

So you use a trait

Example :

trait Formatter
{
public function format($data)
{
// Do some stuff
return $data;
}
}

class Mailer
{
use Formatter;

public function send($data)
{
$data = $this->format($data);
// Send your mail
}
}

class Writer
{
use Formatter;

public function write($data)
{
$data = $this->format($data);
// Write in file
}
}

In PHP, traits are like 'mini-classes'.

PHP: When to use Traits and when to use static methods?

After reading the comments on the question, my take on the answer is this:

Traits allow the extending of a class without it being part of the class hierarchy. There's no need for something like class Book extends Loggable, as Book itself is not a Loggable, we just want the Loggable functionality. The functionality within the Loggable could be stuffed in a trait, therefore being able to use the Loggable methods within Book as though you were extending from it.

The advantage of using traits above the use of static methods within classes (or namespaced functions) is that the trait has access to the full class scope, also private members.

The downside of using static functions instead of traits, is tight coupling (dependencies) between the classes, which hurts reusability and can hurt unit testing (for instance when using mock services). Dependencies should be injected at runtime, which indeed increases the effort of instantiating a class/method, but allow better flexibility over the full app. This was a new insight for me.

What is the purpose of using traits to define functions for an interface

Per Sabre's Event Emitter's docs on "Integration into other objects":

To add Emitter capabilities to any class, you can simply extend it.

If you cannot extend, because the class is already part of an existing
class hierarchy you can use the supplied trait.

So in this case, the idea is if you're using your own objects that already are part of a class hierarchy, you may simply implement the interface + use the trait, instead of extending the Emitter class (which you won't be able to).

PHP Traits common and individual (separate) properties scope

Each trait should define the properties that its methods needs.

Relying on undefined properties or properties defined on the class that uses the traits for composition is a bad idea. Hence, having multiple traits that depend on the same properties is also a bad idea.

Each trait should provide all that's it needs to work, logic wise.

Additionally, traits do not have "scope". When using traits, it's as if the code in the trait is copied to the consuming class. In your example, the scope for setOne() and getTwo() is an instance of Bar, that the methods were defined in traits is irrelevant.

The saner way to do this is include whatever properties your trait need in the trait itself:

E.g.:

 trait CanLog {

private LoggerInterface $logger;

public function error(string $data)
{
$this->logger->error($data);
}

public function alert(string $data)
{
$this->logger->error($data);
}
}

This way a consuming class simply imports the behaviour it needs from the trait, complete, and can inject whatever they need in the properties that the trait provides:

class Bar
{
use CanLog;

public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
}

When to use a Trait or a Service?

The concepts are orthogonal, traits and service dependencies are not interchangeable.

The way you propose to use traits in your question is wrong, breaks encapsulation and makes maintenance harder.

This is a clear case for dependency injection.

Create an interface for your MailerService:

interface MailerServiceInterface {
public function message($to, $body);
}
class MailerService implements MailerServiceInterface {
public function message($to, body) {
/* message implementation */
}
}

And your different services should depend on that interface:

class FacebookAuthService
{
private MailerServiceInterface $mailer;

public __construct(MailerServiceInterface $mailer) {
$this->mailer = $mailer;
}
public function connect(User $user) {
$this->mailer->message($user->getEmail(), 'Login Message');
}
}

Now the code is decoupled, your "auth service" only depends on a generic interface that could have multiple implementations, and if you create a new implementation you can simply "inject" the new implementation without having to touch anything in the multiple "auth services" classes.

Compositing these capabilities is not only wrong conceptually (sending mails is not really your auth services responsibility and these classes should know nothing about how these notifications are actually sent), but it's also problematic practically:

In real life your mailer service will also have it's own share of dependencies (e.g. a HTTP client to communicate with an external API, access to some configuration to know what credentials to use when sending the message, etc). And with traits you would have no way to declare these dependencies, and no way to satisfy them without injecting them on each function call.

The general question about "when to use traits and when to use services?" does not make a lot of sense, since as a I said in the beginning the concepts are orthogonal. It would be better phrased as "when to use traits?". To which the answer is: seldom.

But if you want a more general answer with valid use-cases for traits, I point you to this good post with some good examples.

Are traits not simply composition?

No, traits are not simply composition due the fact that the rules by which traits are "pasted" into a class are completely different.

When using Composition, there is no chance for conflicts or methods overwriting because the composite element is a completely isolated unit (an instance of some other class) you interface with via it's public API from within the consuming instance. Also, if you need to provide access from the consuming instance, you'd have to add proxy methods to delegate to the composite element.

Traits on the other hand become part of the API of the very instance they are used in. They are not subsystems in the instance. They are not even instances but just a reusable boilerplate code. One benefit this provides is satisfying interfaces with a trait, as I have shown in Traits in PHP – any real world examples/best practices?



Related Topics



Leave a reply



Submit