Drupal and PHP Traits - Developer Love

As difficult as it is to find any OOP code in Drupal 7 contrib, it does exist in certain modules. See how to use PHP traits when the module's base classes are not entirely up to the task.

Photo of Salvador Molina Moreno
Thu, 2015-02-26 10:00By salva

Admittedly, PHP Traits have been out there for a long time. You'll surely have heard about them, but chances that you've used them are not that high. I remember being a bit excited the first time I could use them as an effective way of solving some inheritance and code reuse issues in one of our internal projects, so I decided I'd share with you my problem, and how I ended up solving it with a PHP trait.

First things first, let me detail the main traits of the problem (Hey, see what I did there?!):

  • Project makes heavy use of custom entities.
  • Custom entities make use of both Field API, and custom properties in the entity table.
  • The entity forms use the Field API for "standard" fields, and Form API for the custom properties.
  • I'm using the Entity Operations (EO) module classes to construct the base form classes for the entities.
  • The multiple levels of class inheritance in the EO module introduce some difficulties to extend classes in a clean and reusable manner. 

With this in mind, let's see a diagram showing the inheritance in place for one of my forms ("DPlannerEntityFormAddProject"):

PHPStorm Class Diagram

The Goal

Ultimately, my problem was that I needed to introduce some logic affecting both 'add', 'edit' and 'delete' forms. There are several ways to do this, of course, but I put two restrictions in place, with maintainability in mind:

  • EO module shouldn't be modified.
  • I shouldn't make the relationships of all those classes any more complex (hence introducing another base class to merge the logic shared by 'edit' and 'add' classes, to split it up again, is a no-go).

A solution using PHP Traits

For the interest of keeping the post simple, I'm going to assume you already know what a PHP Trait is. If you don't, this post does a great job explaining them, detailing some pros and cons, and the differences with interfaces and abstract classes. To keep things understandable, for now, let's say that a "Trait is a set of methods encapsulated in a way that makes them usable by any class, without having to make the class extend a particular class". Let's have a look at the Trait I wrote to make certain code reusable by my 'add' and 'edit' forms:

trait DPlannerEntityFormTrait {
   * Gets the path to redirect the user to after form submission.
  function getFormSubmitRedirect($entity_type, $entity) {}
   * Sets the path to redirect the user to after form submission.
  function setFormSubmitRedirect($path = NULL) {}
   * Quick and dirty method to sort elements of the form in the desired order.
  public function sortForm($elements_order, &$form) {}

Trait explanation

One of the foremost things to note, to avoid confusion, is that my code implements those methods (in fact, you have to, in a Trait); I've just removed the implementation of the methods from the snippet, to keep things short. There's not much to say about it, as you can see. I needed to be able to set a redirect in most of my forms, which is very different based on the entity type being saved, and on the operation being performed on it ('add', 'edit', 'delete'). I also needed to add some logic to do some "magic" sorting of form elements.

Before writing the Trait, I had to duplicate the exact same code in every class where I wanted to use it. After putting all that code in a Trait, all I have to do in my form classes, is to add this:

use DPlannerEntityFormTrait;

And there I go. I can call any of the methods above from my form classes, without having to worry about duplication.

Wrapping up

I hope you found this example useful and clear, and it's helped you to understand the use of PHP Traits, and how they can help you to enhance code reuse in your classes, work around PHP's single-inheritance limitations, and writing code easier to maintain (as long as you're not abusing them). 

Note that you can find some debate on the net around the use of Traits. As I see them, they're an useful language feature and have their use case, but they shouldn't be abused. Keep in mind that if there are better design options, you might end up hurting maintainability instead of improving it! In this particular case, the restrictions in place made me think a Trait was a clean, suitable, and impactless solution to my problem. And I still think that way, but maybe in a few years time I find out that I was wrong.

So, just be aware of them and have them in your toolset, as they can be definitely handy at some point!