user338717
user338717

Reputation: 77

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

Sorry if this is a duplicate question or a common design principle, I have searched around but was unable to find any answers to this question. I'm probably just searching with the wrong keywords.

I have been looking at a popular library Sabre/Event (https://sabre.io/event/) and in the code there is a simple class/inheritance model that I am trying to understand:

The class EventEmitter implements EventEmitterInterface and uses EventEmitterTrait (see below for code).

There is a comment in EventEmitterTrait above the class which says:

* Using the trait + interface allows you to add EventEmitter capabilities
* without having to change your base-class.

I am trying to understand why this comment says this, and why it allows adding capabilities without changing the base class, and how that is different from just putting the routines into EventEmitter itself.

Couldn't you just extend EventEmitter and add capabilities in the derived class?

Simplified code:

// EventEmitter.php
class EventEmitter implements EventEmitterInterface {
    use EventEmitterTrait;
}


// EventEmitterInterface.php
interface EventEmitterInterface {
    // ... declares several function prototypes
}

// EventEmitterTrait.php
trait EventEmitterTrait {
    // ... implements the routines declared in EventEmitterInterface
}

Upvotes: 3

Views: 977

Answers (4)

Sherif
Sherif

Reputation: 11943

You're basically asking two questions here.

  1. What are interfaces and why are they useful?
  2. What are traits and why are they useful?

To understand why interfaces are useful you have to know a little about inheritance and OOP in general. If you've ever heard the term spaghetti code before (it's when you tend to write imperative code that's so tangled together you can hardly make sense of it) then you should liken that to the term lasagna code for OOP (that's when you extend a class to so many layers that it becomes difficult to understand which layer is doing what).

1. Interfaces

Interfaces diffuse some of this confusion by allow a class to implement a common set of methods without having to restrict the hierarchy of that class. we do not derive interfaces from a base class. We merely implement them into a given class.

A very clear and obvious example of that in PHP is DateTimeInterface. It provides a common set of methods which both DateTime and DateTimeImmutable will implement. It does not, however, tell those classes what the implementation is. A class is an implementation. An interface is just methods of a class sans implementation. However, since both things implement the same interface it's easy to test any class that implements that interface, since you know they will always have the same methods. So I know that both DateTime and DateTimeImmutable will implement the method format, which will accept a String as input and return a String, regardless of which class is implementing it. I could even write my own implementation of DateTime that implements DateTimeInterface and it is guaranteed to have that method with that same signature.

So imagine I wrote a method that accepts a DateTime object, and the method expects to run the format method on that object. If it doesn't care which class, specifically, is given to it, then that method could simply typehint its prototype as DateTimeInterface instead. Now anyone is free to implement DateTimeInterface in their own class, without having to extend from some base class, and provide my method with an object that's guaranteed to work the same way.


So in relation to your EventEmitter example, you can add the same capabilities of a class (like DateTime) to any class that might not even extend from DateTime, but as long as we know it implements the same interface, we know for sure it has the same methods with the same signatures. This would mean the same thing for EventEmitter.

2. Traits

Traits, unlike interfaces, actually can provide an implementation. They are also a form of horizontal inheritance, unlike the vertical inheritance of extending classes. Because two completely different class that do not derive from the same base class can use the same Trait. This is possible, because in PHP traits are basically just compiler-assisted copy and paste. Imagine, you literally copied the code inside of a trait and just pasted it into each class that uses it right before compile time. You'd get the same result. You're just injecting code into unrelated classes.

This is useful, because sometimes you have a method or set of methods that prove reusable in two distinct classes even though the rest of those classes have nothing else in common.

For example, imagine you are writing a CMS, where there is a Document class and a User class. Neither of these two classes are related in any meaningful way. They do very different things and it makes no sense for one of them to extend the other. However, they both share a particular behavior in common: flag() method that indicates the object has been flagged by a user for purposes of violating the Terms of Service.

trait FlagContent {
    public function flag(Int $userId, String $reason): bool {
        $this->flagged = true;
        $this->byUserId = $userId;
        $this->flagReason = $reason;

        return $this->updateDatabase();
    }
}

Now consider that perhaps your CMS has other content that's subject to being flagged, like a Image class, or a Video class, or even a Comment class. These classes are all typically unrelated. It probably wouldn't make much sense just to have a specific class for flagging content, especially if the properties of the relevant objects have to be passed around to this class to update the database, for example. It also doesn't make sense for them to derive from a base class (they're all completely unrelated to each other). It also doesn't make sense to rewrite this same code in every class, since it would easier to change it in one place instead of many.

So what seems to be most sensible here is to use a Trait.


So again, in relation to your EventEmitter example, they're giving you some traits you can reuse in your implementing class to basically make it easier to reuse the code without having to extend from a base class (horizontal inheritance).

Upvotes: 3

alithedeveloper
alithedeveloper

Reputation: 720

This is an interesting question and I will try to give my take on it. As you asked,

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

Traits basically gives you the ability to create some reusable code or functionality which can then be used any where in your code base. Now as it stands, PHP doesn't support multiple inheritance therefore traits and interfaces are there to solve that issue. The question here is why traits though ?? Well imagine a scenario like below,

class User
{
  public function hasRatings()
  {
    // some how we want users to have ratings
  }

  public function hasBeenFavorited()
  {
    // other users can follow
  }

  public function name(){}
  public function friends(){}

  // and a few other methods
}

Now lets say that we have a post class which has the same logic as user and that can be achieved by having hasRatings() and hasBeenFavorited() methods. Now, one way would be to simply inherit from User Class.

class Post extends User
{
  // Now we have access to the mentioned methods but we have inherited
  // methods and properties which is not really needed here
}

Therefore, to solve this issue we can use traits.

trait UserActions 
   {
     public function hasRatings()
      {
        // some how we want users to have ratings
      }

      public function hasBeenFavorited()
      {
        // other users can follow
      }

   }

Having that bit of logic we can now just use it any where in the code where ever it is required.

class User
{
  use UserActions;
}

class Post
{
  use UserActions;
}

Now lets say we have a report class where we want to generate certain report on the basis of user actions.

class Report 
{
   protected $user;

   public function __construct(User $user)
   {
     $this->user = $user
   }

   public function generate()
   {
     return $this->user->hasRatings();
   }
}

Now, what happens if i want to generate report for Post. The only way to achieve that would be to new up another report class i.e. maybe PostReport.. Can you see where I am getting at. Surely there could be another way, where i dont have to repeat myself. Thats where, interfaces or contracts come to place. Keeping that in mind, lets redefine our reports class and make it to accept a contract rather than concrete class which will always ensure that we have access to UserActions.

interface UserActionable
{
   public function hasRatings();

   public function hasBeenFavorited();
}



 class Report 
    {
       protected $actionable;

       public function __construct(UserActionable $actionable)
       {
         $this->actionable = $actionable;
       }

       public function generate()
       {
         return $this->actionable->hasRatings();
       }
    }

//lets make our post and user implement the contract so we can pass them
// to report

class User implements UserActionable
{
  uses UserActions;
}

class Post implements UserActionable
{
  uses UserActions;
}

// Great now we can switch between user and post during run time to generate 
// reports without changing the code base

$userReport = (new Report(new User))->generate();
$postReport = (new Report(new Post))->generate();

So in nutshell, interfaces and traits helps us to achieve design based on SOLID principles, much decoupled code and better composition. Hope that helps

Upvotes: 1

Álvaro González
Álvaro González

Reputation: 146450

The Integration into other objects documentation says:

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

I understand it's a workaround when you already have an OOP design you don't want to alter and you want to add event capabilities. For example:

Model -> AppModel -> Customer

PHP doesn't have multiple inheritance so Customer can extend AppModel or Emitter but not both. If you implement the interface in Customer the code is not reusable elsewhere; if you implement in e.g. AppModel it's available everywhere, which might not be desirable.

With traits, you can write custom event code and cherry-pick where you reuse it.

Upvotes: 1

Jeto
Jeto

Reputation: 14927

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).

Upvotes: 1

Related Questions