Dan Lugg
Dan Lugg

Reputation: 20592

PHP and multiple inheritance; I know you can't, but then how do I..?

I understand that multiple inheritance1 is simply not supported in PHP, and while many "hacks" or workarounds exist to emulate it, I also understand that an approach such as object composition is likely more flexible, stable, and understandable than such workarounds. Curiously, PHP's 5.4's traits will be the fitting solution, but we're not quite there yet, are we.

Now, this isn't simply an "amidoinitrite?" question, but I'd like to ensure that my approach makes sense to others.

Given I have classes Action and Event (there are more, but we'll keep it brief) and they both require (near) identical methods, the obvious approach would be; create a common base class, extend and go; they are, after all, conceptually similar enough to constitute being siblings in a class hierarchy (I think)

The problem is Event needs to extend a class (Exception) that itself cannot extend anything. The methods (and properties) all pertain to "attribute" values, we'll call them "options" and "data", where "options" are values stored at class level, and "data" are values stored at instance level.

With exception of (no pun intended) the Exception class, I can simply create a common class that all pertinent objects extend in order to inherit the necessary functionality, but I'm wondering what I can do to avoid the seemingly inevitable code duplication in Event; also, other classes that are not conceptually similar enough to be siblings need this functionality.

So far the answer seems to be, using the object composition approach, create a Data class, and manage it at two points:

Interfaces, named IData and IOption for example, would be implemented by classes needing this functionality. IData simply enforces the instance methods of the Data class on the consumer, and calls would be forwarded to the instance Data property object, whereas IOption would enforce similarly named methods (substitute "data" for "option") and those methods would forward to the static Data property object.

What I'm looking at is something like this (the methods are somewhat naive in appearance, but I've slimmed them for brevity here):

interface IData{

    public function setData($name, $value);

    public function putData($name, &$variable);

    public function getData($name = null);

}

interface IOption{

    public static function initializeOptions();

    public static function setOption($name, $value);

    public static function setOptions(Array $options);

    public static function getOptions($name = null);

}

class Data implements IData{

    private $_values = array();

    public function setData($name, $value){
        $this->_values[$name] = $value;
    }

    public function putData($name, &$variable){
        $this->_values[$name] = &$variable;
    }

    public function getData($name = null){
        if(null === $name){
            return $this->_values;
        }
        if(isset($this->_values[$name])){
            return $this->_values[$name];
        }
        return null;
    }

}

class Test implements IData, IOption{

    private static $_option;
    private $_data;

    public static function initializeOptions(){
        self::$_option = new Data();
    }

    public static function setOption($name, $value){
        self::$_option->setData($name, $value);
    }

    public static function setOptions(Array $options){
        foreach($options as $name => $value){
            self::$_option->setData($name, $value);
        }
    }

    public static function getOptions($name = null){
        return self::$_option->getOptions($name);
    }

    public function __construct(){
        $this->_data = new Data();
    }

    public function setData($name, $value){
        $this->_data->setData($name, $value);
        return $this;
    }

    public function putData($name, &$variable){
        $this->_data->putData($name, $variable);
        return $this;
    }

    public function getData($name = null){
        return $this->_data->getData($name);
    }

}

So where do I go from here? I can't shake the feeling that I'm moving away from good design with this; I've introduced an irreversible dependency between the client classes and the storage classes, which the interfaces can't explicitly enforce.


Edit: Alternatively, I could keep the reference to Data (wherever necessary) public, eliminating the need for proxy methods, thus simplifying the composition. The problem then, is that I cannot deviate from the Data class functionality, say for instance if I need to make getData() act recursively, as this snippet exemplifies:

function getData($name = null){
    if(null === $name){
        // $parent_object would refer to $this->_parent
        // in the Test class, given it had a hierarchal
        // implementation
        return array_replace($parent_object->getData(), $this->_values);
    }
    // ...
}

Of course, this all boils down to separate definitions on a per-class basis, to support any deviation from a default implementation.

I suppose the end-all here, is that I'm having trouble understanding where code duplication is "alright" (or more accurately, unavoidable) and where I can extract common functionality into a container, and how to reference and use the contained functionality across classes, deviating (typically negligibly) where necessary. Again, traits (in my cursory testing on beta) seem to be a perfect fit here, but the principle of composition has existed long before 5.4 (and PHP entirely for that matter) and I'm certain that there is a "classic" way to accomplish this.


1. Interestingly, the page for multiple inheritance at Wikipedia has been flagged for copyright investigation. Diamond problem seemed like a fitting substitute.

Upvotes: 3

Views: 491

Answers (1)

Peter Horne
Peter Horne

Reputation: 6846

EDIT: I've just read your question again and you seem to be suggesting that you are actually using the getters and setters to manipulate the data. If this is the case then could you provide me with more detail on what it is that you're trying to achieve. I suspect that how you've decided to model your objects and data is what has led you to this situation and that an alternative would solve the problem.

You don't need multiple inheritance. You don't even need most of the code you've written.

If the purposes of classes 'Data' and 'Option' is to simply store data then use an array. Or, if you prefer the syntax of an object cast the array to an object or an instance of stdClass:

$person = (object)array(
    'name' => 'Peter',
    'gender' => 'Male'
);

OR

$person = new stdClass;
$person->name = 'Peter';
$person->gender = 'Male';

Having a whole bunch of getters and setters that don't actually do anything to the data are pointless.

Upvotes: 1

Related Questions