We are serious
about web platforms

Centurion mag, this is where you'll find release announcements, developer tips, and periodic updates from the Centurion team.

Horizontal inheritance

Written by Mathias Desloges in Behind the scene on 25/11/10

Sometimes during the development of Centurion we faced issues like “how can I avoid code duplication when I want the same feature for two classes and each of these classes extends a particular class of the Zend Framework?” If you use Centurion or any other PHP framework you may be familiar with this kind of problems. After investigating around us, we found that the best solution was to implement kind of trait (or mixin classes for those who know about ruby). In PHP we do not have such mechanism provided by the language so we had to implement it our self.

Just a little reminder about trait:

Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine "policy" or "implementation details". - Bjarne Stroustrup

If you are not familiar with this concept, the following example should help you understand.

Much of our model should have a slug (for SEO friendly URL). The generation of the slug should be automatic and transparent for the end user. If you don’t have a trait like mechanism, you must implement the feature in each model. Traits give you the ability to implement the functionality in one place and share this implementation between several classes.

We create a new Trait class like this:

class Core_Traits_Slug_Model_DbTable_Row 
       extends Centurion_Traits_Model_DbTable_Row_Abstract
{
    public function init(){
        parent::init();
        
        Centurion_Signal::factory('pre_save')
            ->connect(array ($this, 'preSave'), $this->_row);
    }

   public function preSave()
    {
        // Implementation detail for slug
    }
}

What happened in this script? The init function (called in the constructor) attach a listener to the signal “pre_save”: each time a “save” is made on the row (insert or update) the “preSave()” method is executed and may change the data to be saved (the slug column).

 

So now we just have to tell the model class that it should inherit behavior from the trait “slug”. Because a class cannot have more than one parent class, we use interface to associate a class with a trait

class Octave_Model_DbTable_Row_Project 
       extends Centurion_Db_Table_Row_Abstract 
       implements Core_Traits_Slug_Model_DbTable_Row_Interface

In the previous example, the “slug” trait implements an automatic behavior, which is transparent for the developer and the end user. But trait can also be used to extend functionality of an object (adding methods, and properties). Take a look at this UML scheme:

In this example, inside the “_protectedMethod” you can transparently call the method “doSomeStuff” (from the trait class). Or inside the “doSomeStuff” method you can also call the “_protectedMethod” or the “_parentProtectedMethod” as if the method “doSomeStuff” were totally part of the class implementing the trait.

Behind the scene, in the constructor of a class, we check for interface that match a “trait” and then we add an instance of the trait class in an internal queue. Inside the magical method __call, __get, __set we check if “traits” registered in the queue handles the requested method / property. And we also implement a delegate design pattern to allow method from the trait class access method from the “children class”

This way a developer using an object (which class inherits a trait) can use extended behavior defined inside the trait without thinking about the implementation.

Tags: Centurion , PHP , Zend Framwork , design pattern , inheritance

0 comments

Add a comment

Please, fill in the field with the charactere 8 of this string gyjybyfy
 
 
Fork me on Github