Traits and Multiple Inheritance

Up until PHP 5.4, there was no multiple inheritance; every line of OO code had to be written with single inheritance in mind. So when PHP 5.4 was released two years ago, many developers asked, “Why would I need Traits?”. This is a good question to ask, we have gotten by without multiple inheritance all this time, we rarely say “Man I wish I had multiple inheritance right now”. However, Traits open up a new door, a new potential path for organization and design. Now that it is possible the question will come up more often. In this post I am going to address this with a perhaps trivial example, but one that does work. This assumes the reader understands Traits and Namespaces in their basic form.

The code has been truncated in favor of a shorter article. Nothing import is missing, just getters and setters really. The full set is available here: TraitsAndMultipleInheritance-Code

 

Database

Let us say that we have a basic database setup (perhaps using Symfony2, CodeIgnitor, or your favorite framework). Maybe we have news, maybe we have announcements, and maybe we have videos. These three things. One some of them we can comment, and on all of them we are going to collect stats.

A basic schema could look like this:

Database Schema

 

 

 

 

 

 

 

The Current Way

First, let us look at the current single inheritance would work. First we would have some base entity, this entity will hold the id (with its getter and setter), since all entities should have a primary key id:

1
2
3
4
5
6
7
<?php
class BaseEntity extends SomeFrameworkClass
{
  protected id;
 
  // ...
}

Once we have this we can extend it. Say, we believe that most entities will require a created date and an updated date. We could add this into the base entity, but let us say that for the purpose of this example, we believe there may be a time that we use the base entity, but do not want these dates. So, we would extend the base entity:

1
2
3
4
5
6
7
8
<?php
class Timestampable extends BaseEntity
{
  protected $lastUpdated;
  protected $created;
 
  // ...
}

Now we are all set. But… what about commenting? Our db schema shows that we are using comment sets. This allows us to abstractly assign comments to different features without caring which feature they are from. The schema uses enum types to track where the comments come from, but that ultimately is for management, not for the relationships that ultimately pull the comments. (More on using relationship sets in the future perhaps).

So off we go, extending again:

1
2
3
4
5
6
7
<?php
class Commentable extends Timestampable
{
  protected commentSetId;
 
  // ...
}

We could stop here, but to exaggerate the issue, we are going to add stats. Everyone wants to know if something has been viewed, liked, or shared “X” number of times. So, we can extend the Commentable class to build on that:

1
2
3
4
5
6
7
<?php
class Shareable extends Commentable
{
  protected statSetId;
 
  // ...
}

Now this could all be avoided, by just defining each individual Entity with all of these attributes, and not doing it this way. But the whole point of OO is to be modular. However, as you can see, it gets bloated quickly. Don’t get me wrong…we can define our News entity now, and extend it from Shareable, and we are good, since Shareable has, Commentable, Timestampable, and the BaseEntity:

1
2
3
4
5
6
7
8
9
10
<?php
class News extends Shareable
{
  protected $article;
  protected $headerImage;
  protected $title;
  protected $tags;
 
  // ...
}

TraitsAndMultipleInheritance-EntityHierarchyNow, here is the problem. The next object, is Announcements, I want Announcements to be shareable, but I don’t want people commenting on them. That means I have to extend the Timestamable into say, Shareable2 so there are no comment pieces. After a while, this starts to get large, especially if many different permutations are required. Many have overcome this obstacle by using interfaces. Multiple interfaces can be implemented by a class, the problem here, is that an interface can only specify what methods should exist in the implementing class. They cannot actually contain any code themselves. So while your class will throw an error if it is missing something, duplicate code still must be written. Now, there are certainly times where this is necessary. Many times a single method in a class will be different depending on the purpose of the class, even if it does the same type of task, but in the case of our example, we would just have duplicate code.

Here is the Announcement entity mentioned, as you can see it extends Shareable, which has comments, we would have to generate Shareable2 as mentioned above in order keep commenting out of the Announcements entity and database:

1
2
3
4
5
6
7
8
9
<?php
class Announcement extends Shareable2
{
  protected $announcement;
  protected $title;
  protected $tags;
 
  // ...
}

Also, if you notice, tags are a commonality between these as well. With so much shared code and potential need for modular behavior, we must turn to Traits for a clean solution.

The Trait Way

Traits act much like a normal class, you can add attributes and methods, you can extend them, you can use a trait within another trait as well. They are quite flexible that way. It is how they are used that ultimately gets them noticed.

To start, we will convert our Timestampable, Commentable, and Shareable classes to traits. each trait is standalone, and does not extend from anything. We will throw in a Taggable trait for good measure as well.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
trait Updated
{
  protected $lastUpdated;
 
  // ...
}
 
trait Created
{
    protected $created;
 
   // ...
}
 
trait Commentable
{
  protected commentSetId;
 
  // ...
}
 
trait Shareable
{
  protected statSetId;
 
  // ...
}
 
 
trait Taggable
{
  protected $tags;
 
  // ...
}

We can also combine traits into commonly used groups:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
trait Timestampable
{
  use Created;
  use Updated;
}
 
trait FullFeatureEntity
{
  use Timestampable;
  use Shareable;
  use Commentable;
  use Taggable;
}

Once these are defined, using them is easy enough. Our News and Announcement entities will now extend the BaseEntity, and “use” the traits, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class NewsWithTraits extends BaseEntity
{
  use FullFeatureEntity;
 
  protected $article;
  protected $headerImage;
  protected $title;
 
  // ...
}
 
class AnnouncementWithTraits extends BaseEntity
{
 
  use Created;
  use Taggable;
  use Shareable;
 
  protected $announcement;
  protected $title;
 
  // ...
}

Now as you can see, the AnnouncementWithTraits entity has Created, Taggable, and Shareable, but not Updated, or Commentable. This is because we don’t want comments on AnnouncementsWithTraits and we only plan to post them once and not update them. So those two things aren’t needed. There is no bloat from multiple extends and as little duplicate code as possible (Just the “use” statements of the traits).

TraitsAndMultipleInheritance-TraitHierarchy

This is the new paradigm, and honestly, it is neat and clean. I really don’t mind it.

I never did create the video object detailed in the db mock-up, feel free to do it as an exercise to get a feel for this design methodology.

You can leave a response, or trackback from your own site.
Subscribe to RSS Feed Subscribe to Tumblr Follow me on Twitter!