Playing with traits in PHP 5.4alpha1

PHP 5.4alpha1 was released the other day with some cool new features. One of them is an implementation of traits. From the RFC:

Traits is 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.

There are some nice examples over at the RFC, and I will provide another example in this post.

But first, to be able to play with this we need to build PHP. It’s the usual ./configure; make; make test; dance so it should not pose too many problems. The make test step is optional, but every time you skip that step, God creates another Perl programmer, so please, make test. If you run into any problems building PHP you will have to look elsewhere for answers. I won’t cover that in this post.

Now that you have built PHP, lets start writing some code.

When working on a framework (or something else for that matter), you will most likely need to introduce parameters for some of your classes, be it database adapters or a storage adapter. All these classes will most likely have the following methods:

  • setParams(array $params)
  • getParams()
  • setParam($key, $value)
  • getParam($key)

The implementation of these methods will most likely be the same. If you put the actual implementation of these methods in many classes you’re not DRY anymore. Since PHP does not support multiple inheritance you will have to make a base class with the implementation of these methods, and have other classes ultimately extend this base class. Now you are relatively DRY, but this solution is not optimal(and sometimes not possible at all).

Enter traits, the newest superhero on the PHP street, or something like that.

Now, I’ll create two classes that needs to support parameters; Cogo\Database\MySQL and Cogo\Storage\Filesystem. They will each extend Cogo\Database and Cogo\Storage respectively that will have the parameter-related methods.

<?php
namespace Cogo;

abstract class Database {
    private $params;

    public function __construct(array $params) {
        $this->setParams($params);
    }

    private function setParams(array $params) {
        $this->params = $params;
    }

    public function getParams() {
        return $this->params;
    }

    protected function setParam($key, $value) {
        $this->params[$key] = $value;
    }

    public function getParam($key) {
        return (isset($this->params[$key]) ? $this->params[$key] : null);
    }

    protected function someDatabaseRelatedMethod() {
        // ...
    }

    abstract protected function someOtherDatabaseRelatedMethod();
}
<?php
namespace Cogo\Database;
use Cogo\Database;

class MySQL extends Database {
    protected function someOtherDatabaseRelatedMethod() {
        // ...
    }
}
<?php
namespace Cogo;

abstract class Storage {
    private $params;

    public function __construct(array $params) {
        $this->setParams($params);
    }

    public function setParams(array $params) {
        $this->params = $params;
    }

    public function getParams() {
        return $this->params;
    }

    public function setParam($key, $value) {
        $this->params[$key] = $value;
    }

    public function getParam($key) {
        return (isset($this->params[$key]) ? $this->params[$key] : null);
    }

    protected function someFilesystemRelatedMethod() {
        // ...
    }

    abstract protected function someOtherFilesystemRelatedMethod();
}
<?php
namespace Cogo\Storage;
use Cogo\Storage;

class Filesystem extends Storage {
    protected function someOtherFilesystemRelatedMethod() {
        // ...
    }
}

As you can see the parameter-related methods are almost the same. The difference is that the Cogo\Database class wants to have its set methods private and protected.

One possibility would be to create a base class for the parameter handling, and have the two abstract classes extend that one, but because multiple inheritance is not possible in PHP it’s easy to grind to a halt using this solution. In the end you might end up with a huge base class with loads of logic simply because it has some logic that you want to share across many other classes.

The base class would also have to have private on the set methods since you can’t override a method with a stricter visibility, only the other way around.

Using traits, this can be solved in a more elegant fashion. Traits also support changing visibility of the methods defined in the trait, so Cogo\Database can keep its private and protected implementations. Here is how the trait can look like:

<?php
namespace Cogo;

trait ParamsHandler {
    private $params;

    public function __construct(array $params) {
        $this->setParams($params);
    }

    public function setParams(array $params) {
        $this->params = $params;
    }

    public function getParams() {
        return $this->params;
    }

    public function setParam($key, $value) {
        $this->params[$key] = $value;
    }

    public function getParam($key) {
        return (isset($this->params[$key]) ? $this->params[$key] : null);
    }
}

Notice that the trait now contains all logic regarding parameters (including the $params property and the constructor). The RFC states that:

Since Traits do not contain any state/properties …

This seems to work after all. The RFC was created a while ago and it no longer matches the actual implementation 100% obviously.

Now that we have a trait with the logic we want in both our abstract classes, let’s use it:

<?php
namespace Cogo;

abstract class Database {
    use ParamsHandler {
        setParams as private;
        setParam as protected;
    }

    protected function someDatabaseRelatedMethod() {
        // ...
    }

    abstract protected function someOtherDatabaseRelatedMethod();
}
<?php
namespace Cogo;

abstract class Storage {
    use ParamsHandler;

    protected function someFilesystemRelatedMethod() {
        // ...
    }

    abstract protected function someOtherFilesystemRelatedMethod();
}

As you can see we have replaced the implementation of the parameter methods from both abstract classes with a couple of use statements. In the Cogo\Storage class we have used the methods from the trait as-is, which means they are all public. In the Cogo\Database class we have changed the visibility of two methods to match the initial implementation. We have now managed to keep ourselves DRY, as well as not creating a base class that the two abstract classes extends.

This is just a simple example of how to use traits. They can do loads more, and there are issues like for instance conflict resolution which is not mentioned in this post at all. I will probably post more examples of traits (and perhaps other 5.4 features) later on. In the meantime you should read the RFC, and try it out for yourself.

You will find all the code mentioned in this post over at my php-traits-examples repository at GitHub. Feel free to play around with it, and if you have other examples on how to use traits please fork the project and send me a pull request so I can include it (and learn from it).

Advertisements
This entry was posted in PHP, Technology and tagged , , . Bookmark the permalink.

7 Responses to Playing with traits in PHP 5.4alpha1

  1. smarr says:

    Hi:

    (Disclaimer: I wrote the initial RFC and patch for traits in PHP.)

    Was just googling how people are actually using traits in the wild. Good to see that people look at it, and thanks for the blog post which hopefully will motivate others to look at it, too.

    We are now in the alpha stage and there is still time to work on details. So, please report back any issues you find.

    One remark to you post. The state issue is still true. Traits do not provide any mechanism to handle state. What you experience is one of the key ideas of traits. The are flattened/folded into the class that use them. Thus, when you access a property in a trait, you will eventually access the property in an object, which is defined/definable at the class level.

    This works for many cases as expected. It only gets hairy if you have multiple traits trying to use the same property for different purposes. And for that, we do not have yet a convincing solution.

    Best regards
    Stefan

  2. Stefan,

    Well, since you asked, would be great to get more eyes on this feature request (trait checks type of consuming class):
    https://bugs.php.net/bug.php?id=55613

  3. Pingback: PHP 5.4.0 veröffentlicht | Netw0rk.eu

  4. Pingback: PHP 5.4.0 released! Neue Funktionen | PHP Gangsta - Der PHP Blog mit Praxisbezug

  5. Pingback: PHP Traits: Good or Bad? | PJExploration

  6. Pingback: PHP Master | PHP Traits: Good or Bad?

  7. Pingback: PHP Traits: Good or Bad? « WORLDPC.US

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s