DEV Community

Sarfraz Ahmed
Sarfraz Ahmed

Posted on • Originally published at codeinphp.github.io on

Sharing Functionality Using Traits in PHP

Official documentation defines traits as:

Traits are a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies. The semantics of the combination of Traits and classes is defined in a way which reduces complexity, and avoids the typical problems associated with multiple inheritance and Mixins.

So what does this mean ? Let's understand through practical example. Imagine we have chat application and database contains two tables; users and messages and we want to be able to have CRUD functionality for each of those entities. Here is how model class for User looks:

class User {
    protected $table = 'users';

    public function getAll() {
        return DB::getAll($this->table);
    }

    public function get($id) {
        return DB::get($this->table, $id);
    }

    public function add($data) {
        return DB::add($this->table, $data);
    }

    public function update($id) {
        return DB::update($this->table, $id);
    }

    public function remove($id) {
        return DB::remove($this->table, $id);
    }
}

Message model class:

class Message {
    protected $table = 'messages';

    public function getAll() {
        return DB::getAll($this->table);
    }

    public function get($id) {
        return DB::get($this->table, $id);
    }

    public function add($data) {
        return DB::add($this->table, $data);
    }

    public function update($id) {
        return DB::update($this->table, $id);
    }

    public function remove($id) {
        return DB::remove($this->table, $id);
    }
}

At first glance, we can see that both of these classes have exactly the same code for those five methods with only difference of $table being different. This smells and to follow the principle of Don't Repeat Yourself, we can use inheritance of course. So we create a new abstract class that will contain common functionality in the form of those five methods and then both classes would extend this base class:

abstract class Model {     
    public function getAll() {
        return DB::getAll($this->table);
    }

    public function get($id) {
        return DB::get($this->table, $id);
    }

    public function add($data) {
        return DB::add($this->table, $data);
    }

    public function update($id) {
        return DB::update($this->table, $id);
    }

    public function remove($id) {
        return DB::remove($this->table, $id);
    }
}

And now our User and Message classes look like this:

class User extends Model {
    protected $table = 'users';
}

class Message extends Model {
    protected $table = 'messages';
}

And that's it, now both of these classes inherit those five methods from the base Model class, no need to repeat them now, perfect !

Now imaging that suddenly clients requires that some of the fields in both of those tables should be encrypted. For that we create a class that contains two methods encrypt() and decrypt():

class Encoder {
    public static function encrypt($text, $key) {
        // code to encrypt text
    }

    public static function decrypt($text, $key) {
        // code to decrypt text
    } 
}

But how do we use it now ? First thought would be to use it as needed:

$user = new User();
$user->add(array('email' => 'foo@bar.com', 'password' => Encoder::encrypt('some_password', 'key')));

This sounds okay but wouldn't it be better whenever we add a user, it should automatically apply encrypt() for the password field or other fields that we want? It would certainly help us save time and keep from repeating code of encrypt() and decrypt() whenever we operated on such field. So to achieve that we extend our classes with this functionality as well:

// wrong code...
class User extends Model extends Encoder {
    protected $table = 'users';
}

Of course we can't do above. We can't extend a class with multiple classes since in PHP you can only do single inheritance.

So another thought that comes to mind is to extend Modelclass instead:

abstract class Model extends Encoder {
    // ....
}

And this would certainly work and we would have Encoder functionality available in all our model classes where we can use it the way we need. But think about it. Is it good idea to have Model and Encryption functionality mixed up ? They are totally different things and it doesn't sound like good idea to put encryption logic into your business logic. But then how ? We can use traits !

So let's convert previous Encoder class into a trait:

trait Encoder {
    public static function encrypt($text, $key) {
        // code to encrypt text
    }

    public static function decrypt($text, $key) {
        // code to decrypt text
    } 
}

And to use that trait in some other classes, we need to use it using use Encoder; syntax, so let's add this functionality in our classes:

class User extends Model {
    use Encoder;
    protected $table = 'users';
}

class Message extends Model {
    use Encoder;
    protected $table = 'messages';
}

And now not only our classes inherit functionality from base Model class but also can use the functionality of Encoder trait. We were also able to separate encryption logic from core model logic. So inside our User and Message classes, we can now use $this->encrypt() or $this->decrypt() methods as though they were defined in them (Actually that's what PHP does at runtime). We have been able to overcome the shortcoming of single inheritance.

So now when you re-look the definition of traits given at top of this post, it should make better sense. So traits are noting but a way of code-reuse. To learn more about traits and their characteristics, please refer to its documentation.

Note: One might argue our User and Message classes are also model logic under the hood but this is just an example to give an idea of traits. We could have had controllers or other concrete classes too where we needed common functionality.

Top comments (0)