PHP OOP - Traits

Last revision:

Overview

Traits let you reuse code in classes that are not part of the same dependency chain.

Traditionally, reusing code from one class in another class has only been possible through inheritance - by creating a class that inherits its methods and properties from a parent class.

But what if you want to reuse code across classes that are not related to each other? That's why PHP added the concept of traits.

A trait looks like a normal PHP class: you give it a name and you add methods, properties, and constants.

 Later on, you "include" the trait in other classes through the use keyword to make the trait's methods, properties, and constants automatically available there.

Traits are only meant for grouping methods, properties, and constants together so you can reuse them later. You can't create instances of your Traits. You can only define them and then use them later in other trait or class definitions.

How to define and use a Trait

Code
<?php

trait Printable {

 function print() {
   print $this->name . PHP_EOL;
 }

}


class Dog {

 use Printable;

 public string $name;

}

$mydog = new Dog();
$mydog->name = "Rex";
$mydog->print();

We declare a trait named Printable with add a method named print().

We then declare a class Dog and "inject" all of methods defined in the Printable trait through the use Printable statement.

How to use multiple Traits

You are not limited to using one single trait. You can use and combine as many traits as you want:

Example 1

Code
<?php

trait Likeable {

 function like() {
   // Code that performs the "like" operation goes here.
   print "Liking..." . PHP_EOL;
 }

}

trait Shareable {

 function share() {
   // Code that performs the "share" operation goes here.
   print "Sharing..." . PHP_EOL;
 }

}

trait Bookmarkeable {

 function bookmark() {
   // Code that performs the "share" operation goes here.
   print "Bookmarking..." . PHP_EOL;
 }

}

class SocialMediaPost {

 use Shareable, Likeable, Bookmarkeable;

 public string $contents;

}

$mypost = new SocialMediaPost();
$mypost->like();
$mypost->share();
$mypost->bookmark();

You are able to call the like(), share(), and bookmark() methods on the SocialMediaPost object because those methods have been "injected" or "kind of inherited" from the Shareable, Likeable, and Bookmarkable traits that are used by the SocialMediaPost class.

Example 2

Let's extend the previous example a little bit:

Code
<?php

trait Likeable {
  
  function like() {
     // Code that performs the "like" operation goes here.
     print "Liking..." . PHP_EOL;
  }

}


trait Shareable {

  function share() {
    // Code that performs the "share" operation goes here.
    print "Sharing..." . PHP_EOL;
  }

}


trait Bookmarkeable {

  function bookmark() {
    // Code that performs the "share" operation goes here.
    print "Bookmarking..." . PHP_EOL;
  }

}

class User {

  use Likeable;

}

class SocialMediaPost {

  use Shareable, Likeable, Bookmarkeable;

  public User $author;
  public string $contents;

}

$eva = new User();
$mypost = new SocialMediaPost();

$mypost->author = $eva;
$mypost->like();
$mypost->share();
$mypost->bookmark();
$mypost->author->like();

We added a User class that uses the Likeable trait, so we will be able to call the like() method on User objects. In other words, we can now like users as well as social media posts.

We added a property of the type User, named $author, to the SocialMediaPost to keep track of who wrote the post.

This example illustrates how traits let you share code across classes that are not related to each other: the SocialMediaPost class and User class do not have a common parent or ancestor, but they can still have certain methods in common.

Difference between Traits and Interfaces

Interfaces and traits are similar in how they are defined and used, but their purpose is different.

Let's compare the two with an example.

If class User implements interface Likeable (which declares a like() method), you can be certain that you can call method like() on any User object. 

However, interfaces don't tell you anything about the implementation of like(). The interface doesn't tell you what like() will do; it only tells you that like() will be present.

If class User uses trait Likeable (which declares and implements a like() method), you know that like() will be available in the User class and you know it will be the exact same implementation as the one provided by the trait.

Differences in terms of code reuse

You use interfaces if you want to be sure that classes contain certain methods, named in a certain way, expecting certain parameters, but you don't care about the implementation (inner workings) of those methods.

You use traits if you want to develop a set of fully implemented methods once, and then share them across multiple classes that are not necessarily in the same inheritance chain (otherwise you probably would have used inheritance).

Traits have no impact on polymorphism

Polymorphism makes the type system flexible. If class Dog extends class Animal, and a certain function accepts an object of the type Animal, it will also accept an object of the type Dog, because a Dog is a subtype of Animal.

If a certain function accepts objects that implement a certain interface, that function will accept any type of object as long as it does indeed implement the specified interface. Interfaces are a key aspect of polymorphism.

Traits, however, have no impact on polymorphism. There is no way to specify that your function accepts any type of object as long as it uses a certain trait, for example.

Nested traits

Classes can use traits, and traits themselves can use other traits as well. In other words, it's possible to create traits that are composed of a number of other traits.

Example

→ Compare the following example with example 1. 
 

Code
<?php

trait Likeable {

  function like() {
    // Code that performs the "like" operation goes here.
    print "Liking..." . PHP_EOL;
  }
  
 }
 
trait Shareable {
 
  function share() {
    // Code that performs the "share" operation goes here.
    print "Sharing..." . PHP_EOL;
  }
 
}
 
trait Bookmarkable {
 
  function bookmark() {
    // Code that performs the "share" operation goes here.
    print "Bookmarking..." . PHP_EOL;
  }

}
 
 
trait Actionable {
 
  use Likeable, Shareable, Bookmarkable;
 
}
 
 
class SocialMediaPost {
 
  use Actionable;
 
  public string $contents;
 
 }
 
 $mypost = new SocialMediaPost();
 $mypost->like();
 $mypost->share();
 $mypost->bookmark();

The output is the same as what example 1 produces, but this time we create a new trait called Actionable, which groups the traits Likeable, Shareable, and Bookmarkable.

We can still use the individual traits Likeable, Shareable, and Bookmarkable elsewhere, or specify all three in the SocialMediaPost class, but we can also make our life easier and use the Actionable trait instead.

Summary

  • Traits are collections of methods and properties that can be reused across classes that are not part of the same inheritance chain (the same class hierarchy).
  • Traits cannot be instantiated.
  • Traits can contain methods, properties, and constants.
  • Traits can use other traits.