Backend Development

Multi Inheritance in PHP Using Traits

As a PHP programmer you might encountered scenarios that you need to extend more than one class to accomplish a certain task but unfortunately php doesn’t support multi inheritance so you can only inherit from one class.

 

However in php 5.4 a new feature added known as Traits. A Trait is kind of like a Mixin in that it allows you to mix Trait classes into an existing class. This means you can reduce code duplication and get the benefits whilst avoiding the problems of multiple inheritance.

 

So What are PHP Traits?

A trait is similar to a class but for grouping methods in a fine-grained and consistent way. It is not allowed to instantiate a trait on its own. So a trait is just a container for a group of methods that you can reuse in another classes.

 

How do Traits work?

A Trait is basically just a way to “copy and paste” code during run time. So when you run your application all trait methods are copied and pasted into the class using them as if they defined in the same class.

 

Traits examples

Declaring a new trait is similar to declaring a new class as shown in the following example:

<?php
trait Logger {
 function log($msg) {
 echo '<pre>';
 echo date('Y-m-d h:i:s') . ':' . '(' . __CLASS__ .  ') ' . $msg . '<br/>';
 echo '</pre>';
 }
}

To use a trait in a class, you use the use keyword. All the trait’s methods are available in the class where it is used. Calling a method of a trait is similar to calling an instance method.

class User{
 use Logger;
 
 
 function __construct(){
      $this->log("A new user created");
 }
}

We can also reuse the Logger trait in the Post class as follows:

class Post{
 use Logger;
 function __construct() {
    $this->log("A new post created");
 }
}

Both User and Post classes reuse methods of the Logger trait, which is very flexible.

We can test our script to see how it works.

$user = new User();
$post = new Post();

 

Using multi traits

A class can use multi traits. Consider that you have a class that needs to import methods from more than one trait as shown in the following example.

trait Logger{
 function log($msg) {
 echo $msg;
 }
}
trait DateHelper{
 function format($format) {
   return date($format);
 }
}


class Formatter{
  use Logger, DateHelper;

   public function __construct()
   {
       $this->log("Post class initiated");
   }

   function create($format) {

      echo $this->format($format);
 } 
}

 

Using traits in another traits

A trait can be composed of other traits by using the use statement in the trait’s declaration. See the following example:

trait Logger{
 function log($msg) {
 echo $msg;
 }
}

trait Post{
   use Logger;

   $this->log("a new Post obj created");
}

In the above code we created a Logger trait. Next we created a new trait Post. to use the Logger in the Post trait we import it the same way we do it in a class using the use statement in the trait’s declaration. Now we able to use the Logger method.

 

PHP trait’s method conflict resolution

If a class uses multiple traits that share the same method name, PHP will raise a fatal error. Fortunately, you can tell PHP which method of  which trait to be used by using inteadof keyword. Let’s take a look at the following example:

<?php
trait FileLogger{
 public function log($msg){
 echo 'File Logger ' . date('Y-m-d h:i:s') . ':' . $msg . '<br/>';
 }
}
 
trait DatabaseLogger{
 public function log($msg){
 echo 'Database Logger ' . date('Y-m-d h:i:s') . ':' . $msg . '<br/>';
 }
}
 
class Logger{
 use FileLogger, DatabaseLogger{
 FileLogger::log insteadof DatabaseLogger;
 }
}
 
$logger = new Logger();
$logger->log('this is a test message #1');
$logger->log('this is a test message #2');

Both FileLogger and DatabaseLogger traits have the same  log() method. In the Logger class, we resolved the method name conflict by specifying that the  log() method of the FileLogger trait will be used instead of the DatabaseLogger‘s.

 

But what about using both methods in the FileLogger and DatabaseLogger traits. you can use alias for the method of the trait within the class that uses the trait.

 

Aliasing trait method

By using aliases for the same method name of multiple traits, you can reuse all the methods in those traits. You use the as keyword to alias a method of a trait to a different name within the class that uses the trait. See the below example

class Logger{
 use FileLogger, DatabaseLogger{
 DatabaseLogger::log as log2DB;
 FileLogger::log insteadof DatabaseLogger;
 }
}
 
$logger = new Logger();
$logger->log('this is a test message #1');
$logger->log2DB('this is a test message #2');

The method  log() of the DatabaseLogger class has a new name ( log2DB)  in the context of the Logger class.

 

So What are typical scenarios for using Traits?

As we see in the examples above traits is a container for group of methods so the typical scenarios for using it to reuse a chunk of code between a set of similar classes that should not inherit from the same abstract class.

 

5 3 votes
Article Rating

What's your reaction?

Excited
1
Happy
1
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Lenvo Genovia
Lenvo Genovia
4 years ago

I edited the last program segment in this article as follows.

class Logger{
use FileLogger, DatabaseLogger{
DatabaseLogger::log as logDB;
FileLogger::log as logFile;
}
}

$logger = new Logger();
$logger->logFile(‘this is a test message #1’);
$logger->logDB(‘this is a test message #2’);

However, I get this error: “Trait method log has not been applied, because there are collisions with other trait methods on Logger.” Why didn’t it work? The trait method names are now unique (or so it looks like?), so I thought it would be enough to prevent collisions without having the need to use the “insteadof” keyword.