Sharing Functionality Using Traits in PHP
Sarfraz Ahmed April 26, 2015 01:04 AMOfficial 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 Model
class 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
andMessage
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.